From 0c0141cd71518884753f948342bb7faded7f15fc Mon Sep 17 00:00:00 2001 From: Retera Date: Mon, 26 Oct 2020 08:34:33 -0400 Subject: [PATCH] Update ui --- .../etheller/warsmash/WarsmashGdxMapGame.java | 3 +- .../warsmash/viewer5/SkeletalNode.java | 15 +- .../handlers/mdx/MdxComplexInstance.java | 1511 +++++++++-------- .../viewer5/handlers/w3x/War3MapViewer.java | 75 - .../handlers/w3x/rendersim/RenderUnit.java | 4 + .../commandbuttons/CommandButtonListener.java | 2 +- .../CommandCardPopulatingAbilityVisitor.java | 52 +- .../handlers/w3x/simulation/CSimulation.java | 17 +- .../handlers/w3x/simulation/CUnit.java | 149 +- .../w3x/simulation/CUnitStateListener.java | 6 +- .../w3x/simulation/abilities/CAbility.java | 7 +- .../simulation/abilities/CAbilityAttack.java | 29 +- .../abilities/CAbilityColdArrows.java | 117 ++ .../simulation/abilities/CAbilityGeneric.java | 84 + .../simulation/abilities/CAbilityMove.java | 32 +- .../simulation/abilities/CAbilityVisitor.java | 4 + .../behaviors/CAbstractRangedBehavior.java | 108 ++ .../w3x/simulation/behaviors/CBehavior.java | 13 + .../simulation/behaviors/CBehaviorAttack.java | 111 ++ .../simulation/behaviors/CBehaviorFollow.java | 38 + .../{orders => behaviors}/CBehaviorMove.java | 78 +- .../simulation/behaviors/CBehaviorPatrol.java | 37 + .../simulation/behaviors/CBehaviorStop.java | 22 + .../simulation/behaviors/CRangedBehavior.java | 7 + .../w3x/simulation/data/CAbilityData.java | 21 +- .../w3x/simulation/data/CUnitData.java | 736 ++++---- .../w3x/simulation/orders/CBehavior.java | 23 - .../simulation/orders/CBehaviorAttack.java | 177 -- .../simulation/orders/CBehaviorPatrol.java | 96 -- .../w3x/simulation/orders/CBehaviorStop.java | 22 - .../w3x/simulation/orders/COrder.java | 18 + .../w3x/simulation/orders/COrderNoTarget.java | 33 + .../simulation/orders/COrderTargetPoint.java | 57 + .../simulation/orders/COrderTargetWidget.java | 51 +- .../players/CPlayerUnitOrderExecutor.java | 78 +- .../players/CPlayerUnitOrderListener.java | 6 +- .../handlers/w3x/ui/CommandCardIcon.java | 16 +- .../viewer5/handlers/w3x/ui/MeleeUI.java | 97 +- .../command/CommandCardCommandListener.java | 2 - 39 files changed, 2194 insertions(+), 1760 deletions(-) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java rename core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/{orders => behaviors}/CBehaviorMove.java (87%) create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java delete mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java create mode 100644 core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java index 64375dd..55e06f0 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapGame.java @@ -309,7 +309,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv this.font.setColor(Color.YELLOW); final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond(); this.glyphLayout.setText(this.font, fpsString); - this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, 1100); + this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, + 1100 * this.meleeUI.getHeightRatioCorrection()); this.batch.end(); Gdx.gl30.glEnable(GL30.GL_SCISSOR_TEST); diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index bf6a231..134d393 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -21,6 +21,8 @@ public abstract class SkeletalNode extends GenericNode { public boolean billboardedY; public boolean billboardedZ; + public Matrix4 localBlendMatrix; + public SkeletalNode() { this.pivot = new Vector3(); this.localLocation = new Vector3(); @@ -33,6 +35,7 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldRotation = new Quaternion(); this.inverseWorldScale = new Vector3(); this.localMatrix = new Matrix4(); + this.localBlendMatrix = new Matrix4(); this.worldMatrix = new Matrix4(); this.dontInheritTranslation = false; this.dontInheritRotation = false; @@ -66,7 +69,7 @@ public abstract class SkeletalNode extends GenericNode { this.billboardedZ = false; } - public void recalculateTransformation(final Scene scene) { + public void recalculateTransformation(final Scene scene, final float blendTimeRatio) { final Quaternion computedRotation; Vector3 computedScaling; @@ -135,6 +138,12 @@ public abstract class SkeletalNode extends GenericNode { RenderMathUtils.fromRotationTranslationScaleOrigin(computedRotation, this.localLocation, computedScaling, this.localMatrix, this.pivot); + if (!Float.isNaN(blendTimeRatio) && (blendTimeRatio > 0)) { + for (int i = 0; i < this.localMatrix.val.length; i++) { + this.localMatrix.val[i] = (this.localBlendMatrix.val[i] * blendTimeRatio) + + (this.localMatrix.val[i] * (1 - blendTimeRatio)); + } + } RenderMathUtils.mul(this.worldMatrix, this.parent.worldMatrix, this.localMatrix); @@ -168,6 +177,10 @@ public abstract class SkeletalNode extends GenericNode { this.inverseWorldLocation.z = -this.worldLocation.z; } + public void beginBlending() { + this.localBlendMatrix.set(this.localMatrix); + } + public void updateChildren(final float dt, final Scene scene) { for (int i = 0, l = this.children.size(); i < l; i++) { this.children.get(i).update(dt, scene); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 093433a..9b05474 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -26,744 +26,775 @@ import com.etheller.warsmash.viewer5.gl.DataTexture; import com.etheller.warsmash.viewer5.handlers.w3x.DynamicShadowManager; public class MdxComplexInstance extends ModelInstance { - private static final float[] visibilityHeap = new float[1]; - private static final float[] translationHeap = new float[3]; - private static final float[] rotationHeap = new float[4]; - private static final float[] scaleHeap = new float[3]; - private static final float[] colorHeap = new float[3]; - private static final float[] alphaHeap = new float[1]; - private static final long[] textureIdHeap = new long[1]; - - public List lights = new ArrayList<>(); - public List attachments = new ArrayList<>(); - public List particleEmitters = new ArrayList<>(); - public List particleEmitters2 = new ArrayList<>(); - public List ribbonEmitters = new ArrayList<>(); - public List> eventObjectEmitters = new ArrayList<>(); - public MdxNode[] nodes; - public SkeletalNode[] sortedNodes; - public int frame = 0; - public float floatingFrame = 0; - // Global sequences - public int counter = 0; - public int sequence = -1; - public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; - public boolean sequenceEnded = false; - public float[] vertexColor = {1, 1, 1, 1}; - // Particles do not spawn when the sequence is -1, or when the sequence finished - // and it's not repeating - public boolean allowParticleSpawn = false; - // If forced is true, everything will update regardless of variancy. - // Any later non-forced update can then use variancy to skip updating things. - // It is set to true every time the sequence is set with setSequence(). - public boolean forced = true; - public float[][] geosetColors; - public float[] layerAlphas; - public int[] layerTextures; - public float[][] uvAnims; - public Matrix4[] worldMatrices; - public FloatBuffer worldMatricesCopyHeap; - public DataTexture boneTexture; - public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; - private float animationSpeed = 1.0f; - - public MdxComplexInstance(final MdxModel model) { - super(model); - } - - @Override - public void load() { - final MdxModel model = (MdxModel) this.model; - - this.geosetColors = new float[model.geosets.size()][]; - for (int i = 0, l = model.geosets.size(); i < l; i++) { - this.geosetColors[i] = new float[4]; - } - - this.layerAlphas = new float[model.layers.size()]; - this.layerTextures = new int[model.layers.size()]; - this.uvAnims = new float[model.layers.size()][]; - for (int i = 0, l = model.layers.size(); i < l; i++) { - this.layerAlphas[i] = 0; - this.layerTextures[i] = 0; - this.uvAnims[i] = new float[5]; - } - - // Create the needed amount of shared nodes. - final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), - MdxNodeDescriptor.INSTANCE); - final List nodes = (List) sharedNodeData[0]; - int nodeIndex = 0; - this.nodes = nodes.toArray(new MdxNode[nodes.size()]); - - // A shared typed array for all world matrices of the internal nodes. - this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); - this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) - .order(ByteOrder.nativeOrder()).asFloatBuffer(); - - // And now initialize all of the nodes and objects - for (final Bone bone : model.bones) { - this.initNode(this.nodes, this.nodes[nodeIndex++], bone); - } - - for (final Light light : model.lights) { - final LightInstance lightInstance = new LightInstance(this, light); - this.lights.add(lightInstance); - this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); - } - - for (final Helper helper : model.helpers) { - this.initNode(this.nodes, this.nodes[nodeIndex++], helper); - } - - for (final Attachment attachment : model.attachments) { - AttachmentInstance attachmentInstance = null; - - // Attachments may have game models attached to them, such as Undead and - // Nightelf building animations. - if (attachment.internalModel != null) { - attachmentInstance = new AttachmentInstance(this, attachment); - - this.attachments.add(attachmentInstance); - } - - this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); - } - - for (final ParticleEmitterObject emitterObject : model.particleEmitters) { - final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); - - this.particleEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { - final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); - - this.particleEmitters2.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { - final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); - - this.ribbonEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final EventObjectEmitterObject emitterObject : model.eventObjects) { - final String type = emitterObject.type; - EventObjectEmitter emitter; - - if ("SPN".equals(type)) { - emitter = new EventObjectSpnEmitter(this, emitterObject); - } else if ("SPL".equals(type)) { - emitter = new EventObjectSplEmitter(this, emitterObject); - } else if ("UBR".equals(type)) { - emitter = new EventObjectUbrEmitter(this, emitterObject); - } else { - emitter = new EventObjectSndEmitter(this, emitterObject); - } - - this.eventObjectEmitters.add(emitter); - - this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); - } - - for (final CollisionShape collisionShape : model.collisionShapes) { - this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); - } - - // Save a sorted array of all of the nodes, such that every child node comes - // after its parent. - // This allows for flat iteration when updating. - final List hierarchy = model.hierarchy; - - this.sortedNodes = new SkeletalNode[nodes.size()]; - for (int i = 0, l = nodes.size(); i < l; i++) { - this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; - } - - // If the sequence was changed before the model was loaded, reset it now that - // the model loaded. - this.setSequence(this.sequence); - - if (model.bones.size() != 0) { - this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); - } - } - - /* - * Clear all of the emitted objects that belong to this instance. - */ - @Override - public void clearEmittedObjects() { - for (final ParticleEmitter emitter : this.particleEmitters) { - emitter.clear(); - } - - for (final ParticleEmitter2 emitter : this.particleEmitters2) { - emitter.clear(); - } - - for (final RibbonEmitter emitter : this.ribbonEmitters) { - emitter.clear(); - } - - for (final EventObjectEmitter emitter : this.eventObjectEmitters) { - emitter.clear(); - } - } - - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { - initNode(nodes, node, genericObject, null); - } - - /** - * Initialize a skeletal node. - */ - private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, - final UpdatableObject object) { - node.pivot.set(genericObject.pivot); - - if (genericObject.parentId == -1) { - node.parent = this; - } else { - node.parent = nodes[genericObject.parentId]; - } - - /// TODO: single-axis billboarding - if (genericObject.billboarded != 0) { - node.billboarded = true; - } else if (genericObject.billboardedX != 0) { - node.billboardedX = true; - } else if (genericObject.billboardedY != 0) { - node.billboardedY = true; - } else if (genericObject.billboardedZ != 0) { - node.billboardedZ = true; - } - - if (object != null) { - node.object = object; - } - - } - - /* - * Overriden to hide also attachment models. - */ - @Override - public void hide() { - super.hide(); - - for (final AttachmentInstance attachment : this.attachments) { - attachment.internalInstance.hide(); - } - } - - /** - * Updates all of this instance internal nodes and objects. Nodes that are - * determined to not be visible will not be updated, nor will any of their - * children down the hierarchy. - */ - public void updateNodes(final float dt, final boolean forced) { - if (!this.model.ok) { - return; - } - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final SkeletalNode[] sortedNodes = this.sortedNodes; - final MdxModel model = (MdxModel) this.model; - final List sortedGenericObjects = model.sortedGenericObjects; - final Scene scene = this.scene; - - // Update the nodes - for (int i = 0, l = sortedNodes.length; i < l; i++) { - final GenericObject genericObject = sortedGenericObjects.get(i); - final SkeletalNode node = sortedNodes[i]; - final GenericNode parent = node.parent; - - genericObject.getVisibility(visibilityHeap, sequence, frame, counter); - - final boolean objectVisible = visibilityHeap[0] > 0; - final boolean nodeVisible = forced || (parent.visible && objectVisible); - - node.visible = nodeVisible; - - // Every node only needs to be updated if this is a forced update, or if both - // the parent node and the generic object corresponding to this node are - // visible. - // Incoming messy code for optimizations! - if (nodeVisible) { - boolean wasDirty = false; - final GenericObject.Variants variants = genericObject.variants; - final Vector3 localLocation = node.localLocation; - final Quaternion localRotation = node.localRotation; - final Vector3 localScale = node.localScale; - - // Only update the local node data if there is a need to - if (forced || variants.generic[sequence]) { - wasDirty = true; - - // Translation - if (forced || variants.translation[sequence]) { - genericObject.getTranslation(translationHeap, sequence, frame, counter); - - localLocation.x = translationHeap[0]; - localLocation.y = translationHeap[1]; - localLocation.z = translationHeap[2]; - } - - // Rotation - if (forced || variants.rotation[sequence]) { - genericObject.getRotation(rotationHeap, sequence, frame, counter); - - localRotation.x = rotationHeap[0]; - localRotation.y = rotationHeap[1]; - localRotation.z = rotationHeap[2]; - localRotation.w = rotationHeap[3]; - } - - // Scale - if (forced || variants.scale[sequence]) { - genericObject.getScale(scaleHeap, sequence, frame, counter); - - localScale.x = scaleHeap[0]; - localScale.y = scaleHeap[1]; - localScale.z = scaleHeap[2]; - } - } - - final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; - - node.wasDirty = wasReallyDirty; - - // If this is a forced update, or this node's local data was updated, or the - // parent node was updated, do a full world update. - if (wasReallyDirty) { - node.recalculateTransformation(scene); - } - - // If there is an instance object associated with this node, and the node is - // visible (which might not be the case for a forced update!), update the - // object. - // This includes attachments and emitters. - final UpdatableObject object = node.object; - - if (object != null) { - object.update(dt, objectVisible); - } - - // Update all of the node's non-skeletal children, which will update their - // children, and so on. - node.updateChildren(dt, scene); - } - } - } - - /** - * Update the batch data. - */ - public void updateBatches(final boolean forced) { - final int sequence = this.sequence; - final int frame = this.frame; - final int counter = this.counter; - final MdxModel model = (MdxModel) this.model; - if (!model.ok) { - return; - } - final List geosets = model.geosets; - final List layers = model.layers; - final float[][] geosetColors = this.geosetColors; - final float[] layerAlphas = this.layerAlphas; - final int[] layerTextures = this.layerTextures; - final float[][] uvAnims = this.uvAnims; - - // Geoset - for (int i = 0, l = geosets.size(); i < l; i++) { - final Geoset geoset = geosets.get(i); - final GeosetAnimation geosetAnimation = geoset.geosetAnimation; - final float[] geosetColor = geosetColors[i]; - - if (geosetAnimation != null) { - // Color - if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { - geosetAnimation.getColor(colorHeap, sequence, frame, counter); - - geosetColor[0] = colorHeap[0]; - geosetColor[1] = colorHeap[1]; - geosetColor[2] = colorHeap[2]; - } - - // Alpha - if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { - geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); - - geosetColor[3] = alphaHeap[0]; - } - } else if (forced) { - geosetColor[0] = 1; - geosetColor[1] = 1; - geosetColor[2] = 1; - geosetColor[3] = 1; - } - } - - // Layers - for (int i = 0, l = layers.size(); i < l; i++) { - final Layer layer = layers.get(i); - final TextureAnimation textureAnimation = layer.textureAnimation; - final float[] uvAnim = uvAnims[i]; - - // Alpha - if (forced || (layer.variants.get("alpha")[sequence] != 0)) { - layer.getAlpha(alphaHeap, sequence, frame, counter); - - layerAlphas[i] = alphaHeap[0]; - } - - // Sprite animation - if (forced || (layer.variants.get("textureId")[sequence] != 0)) { - layer.getTextureId(textureIdHeap, sequence, frame, counter); - - layerTextures[i] = (int) textureIdHeap[0]; - } - - if (textureAnimation != null) { - // UV translation animation - if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { - textureAnimation.getTranslation(translationHeap, sequence, frame, counter); - - uvAnim[0] = translationHeap[0]; - uvAnim[1] = translationHeap[1]; - } - - // UV rotation animation - if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { - textureAnimation.getRotation(rotationHeap, sequence, frame, counter); - - uvAnim[2] = rotationHeap[2]; - uvAnim[3] = rotationHeap[3]; - } - - // UV scale animation - if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { - textureAnimation.getScale(scaleHeap, sequence, frame, counter); - - uvAnim[4] = scaleHeap[0]; - } - } else if (forced) { - uvAnim[0] = 0; - uvAnim[1] = 0; - uvAnim[2] = 0; - uvAnim[3] = 1; - uvAnim[4] = 1; - } - } - } - - public void updateBoneTexture() { - if (this.boneTexture != null) { - this.worldMatricesCopyHeap.clear(); - for (int i = 0, l = this.worldMatrices.length; i < l; i++) { - final Matrix4 worldMatrix = this.worldMatrices[i]; - this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); - this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); - this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); - this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); - this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); - this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); - this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); - this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); - this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); - this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); - this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); - this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); - this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); - this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); - this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); - this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); - } - this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); - } - } - - @Override - public void renderOpaque(final Matrix4 mvp) { - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.opaqueGroups) { - group.render(this, mvp); - } - } - - @Override - public void renderTranslucent() { - if (DynamicShadowManager.IS_SHADOW_MAPPING) { - return; - } - final MdxModel model = (MdxModel) this.model; - - for (final GenericGroup group : model.translucentGroups) { - group.render(this, this.scene.camera.viewProjectionMatrix); - } - } - - @Override - public void updateAnimations(final float dt) { - final MdxModel model = (MdxModel) this.model; - final int sequenceId = this.sequence; - - if (sequenceId != -1) { - final Sequence sequence = model.sequences.get(sequenceId); - final long[] interval = sequence.getInterval(); - final float frameTime = (dt * 1000 * this.animationSpeed); - - final int lastIntegerFrame = this.frame; - this.floatingFrame += frameTime; - this.frame = (int) this.floatingFrame; - final int integerFrameTime = this.frame - lastIntegerFrame; - this.counter += integerFrameTime; - this.allowParticleSpawn = true; - - if (this.floatingFrame >= interval[1]) { - if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) - || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { - this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast - - this.resetEventEmitters(); - } else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation - // mode - final float framesPast = this.floatingFrame - interval[1]; - - final List sequences = model.sequences; - this.sequence = (this.sequence + 1) % sequences.size(); - this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast - this.frame = (int) this.floatingFrame; - this.sequenceEnded = false; - this.resetEventEmitters(); - this.forced = true; - } else { - this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast - this.counter -= integerFrameTime; - this.allowParticleSpawn = false; - } - if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { - hide(); - } - - this.sequenceEnded = true; - } else { - this.sequenceEnded = false; - } - } - - final boolean forced = this.forced; - - if (sequenceId == -1) { - if (forced) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - - // Update the batches - this.updateBatches(forced); - } - } else { - // let variants = model.variants; - - // if (forced || variants.nodes[sequenceId]) { - // Update the nodes - this.updateNodes(dt, forced); - - this.updateBoneTexture(); - // } - - // if (forced || variants.batches[sequenceId]) { - // Update the batches - this.updateBatches(forced); - // } - } - - this.forced = false; - - } - - @Override - protected void updateLights(final Scene scene) { - for (final LightInstance light : this.lights) { - light.update(scene); - } - } - - @Override - protected void removeLights(final Scene scene2) { - for (final LightInstance light : this.lights) { - light.remove(this.scene); - } - } - - /** - * Set the team color of this instance. - */ - public MdxComplexInstance setTeamColor(final int id) { - this.replaceableTextures[1] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - this.replaceableTextures[2] = (Texture) this.model.viewer.load( - "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", - PathSolver.DEFAULT, null); - return this; - } - - @Override - public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { - this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, - PathSolver.DEFAULT, null); - } - - /** - * Set the vertex color of this instance. - */ - public MdxComplexInstance setVertexColor(final float[] color) { - System.arraycopy(color, 0, this.vertexColor, 0, color.length); - - return this; - } - - /** - * Set the sequence of this instance. - */ - public MdxComplexInstance setSequence(final int id) { - final MdxModel model = (MdxModel) this.model; - - this.sequence = id; - - if (model.ok) { - final List sequences = model.sequences; - - if ((id < 0) || (id > (sequences.size() - 1))) { - this.sequence = -1; - this.frame = 0; - this.floatingFrame = 0; - this.allowParticleSpawn = false; - } else { - this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast - this.floatingFrame = this.frame; - this.sequenceEnded = false; - } - - this.resetEventEmitters(); - - this.forced = true; - } - - return this; - } - - /** - * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, - * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay - * spawned effects - */ - public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { - this.sequenceLoopMode = mode; - - return this; - } - - /** - * Get an attachment node. - */ - public MdxNode getAttachment(final int id) { - final MdxModel model = (MdxModel) this.model; - final Attachment attachment = model.attachments.get(id); - - if (attachment != null) { - return this.nodes[attachment.index]; - } - - return null; - } - - /** - * Event emitters depend on keyframe index changes to emit, rather than only - * values. To work, they need to check what the last keyframe was, and only if - * it's a different one, do something. When changing sequences, these states - * need to be reset, so they can immediately emit things if needed. - */ - private void resetEventEmitters() { - /// TODO: Update this. Said Ghostwolf. - for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { - eventObjectEmitter.reset(); - } - } - - @Override - protected RenderBatch getBatch(final TextureMapper textureMapper2) { - throw new UnsupportedOperationException("NOT API"); - } - - public void intersectRayBounds(final Ray ray, final Vector3 intersection) { - this.model.bounds.intersectRay(ray, intersection); - } - - /** - * Intersects a world ray with the model's CollisionShapes. Only ever call this - * function on the Gdx thread because it uses static variables to hold state - * while processing. - * - * @param ray - */ - public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, final boolean onlyUseMesh) { - final MdxModel mdxModel = (MdxModel) this.model; - final List collisionShapes = mdxModel.collisionShapes; - if (!onlyUseMesh) { - for (final CollisionShape collisionShape : collisionShapes) { - final MdxNode mdxNode = this.nodes[collisionShape.index]; - if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { - return true; - } - } - } - if (collisionShapes.isEmpty() || alwaysUseMesh) { - for (final Geoset geoset : mdxModel.geosets) { - if (!geoset.unselectable) { - geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); - if (alphaHeap[0] > 0) { - final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; - if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), - mdlxGeoset.getFaces(), 3, intersection)) { - return true; - } - } - } - } - } - return false; - } - - public void setAnimationSpeed(final float speedRatio) { - this.animationSpeed = speedRatio; - } - - public void setFrame(final int frame) { - this.frame = frame; - this.floatingFrame = frame; - } - - public void setFrameByRatio(final float ratioOfAnimationCompleted) { - if (this.sequence != -1) { - final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); - this.floatingFrame = currentlyPlayingSequence.getInterval()[0] - + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) - * ratioOfAnimationCompleted); - this.frame = (int) this.floatingFrame; - } - } + private static final float[] visibilityHeap = new float[1]; + private static final float[] translationHeap = new float[3]; + private static final float[] rotationHeap = new float[4]; + private static final float[] scaleHeap = new float[3]; + private static final float[] colorHeap = new float[3]; + private static final float[] alphaHeap = new float[1]; + private static final long[] textureIdHeap = new long[1]; + + public List lights = new ArrayList<>(); + public List attachments = new ArrayList<>(); + public List particleEmitters = new ArrayList<>(); + public List particleEmitters2 = new ArrayList<>(); + public List ribbonEmitters = new ArrayList<>(); + public List> eventObjectEmitters = new ArrayList<>(); + public MdxNode[] nodes; + public SkeletalNode[] sortedNodes; + public int frame = 0; + public float floatingFrame = 0; + // Global sequences + public int counter = 0; + public int sequence = -1; + public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP; + public boolean sequenceEnded = false; + public float[] vertexColor = { 1, 1, 1, 1 }; + // Particles do not spawn when the sequence is -1, or when the sequence finished + // and it's not repeating + public boolean allowParticleSpawn = false; + // If forced is true, everything will update regardless of variancy. + // Any later non-forced update can then use variancy to skip updating things. + // It is set to true every time the sequence is set with setSequence(). + public boolean forced = true; + public float[][] geosetColors; + public float[] layerAlphas; + public int[] layerTextures; + public float[][] uvAnims; + public Matrix4[] worldMatrices; + public FloatBuffer worldMatricesCopyHeap; + public DataTexture boneTexture; + public Texture[] replaceableTextures = new Texture[WarsmashConstants.REPLACEABLE_TEXTURE_LIMIT]; + private float animationSpeed = 1.0f; + private float blendTime; + private float blendTimeRemaining; + + public MdxComplexInstance(final MdxModel model) { + super(model); + } + + @Override + public void load() { + final MdxModel model = (MdxModel) this.model; + + this.geosetColors = new float[model.geosets.size()][]; + for (int i = 0, l = model.geosets.size(); i < l; i++) { + this.geosetColors[i] = new float[4]; + } + + this.layerAlphas = new float[model.layers.size()]; + this.layerTextures = new int[model.layers.size()]; + this.uvAnims = new float[model.layers.size()][]; + for (int i = 0, l = model.layers.size(); i < l; i++) { + this.layerAlphas[i] = 0; + this.layerTextures[i] = 0; + this.uvAnims[i] = new float[5]; + } + + // Create the needed amount of shared nodes. + final Object[] sharedNodeData = Node.createSkeletalNodes(model.genericObjects.size(), + MdxNodeDescriptor.INSTANCE); + final List nodes = (List) sharedNodeData[0]; + int nodeIndex = 0; + this.nodes = nodes.toArray(new MdxNode[nodes.size()]); + + // A shared typed array for all world matrices of the internal nodes. + this.worldMatrices = ((List) sharedNodeData[1]).toArray(new Matrix4[0]); + this.worldMatricesCopyHeap = ByteBuffer.allocateDirect(16 * this.worldMatrices.length * 4) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + + // And now initialize all of the nodes and objects + for (final Bone bone : model.bones) { + this.initNode(this.nodes, this.nodes[nodeIndex++], bone); + } + + for (final Light light : model.lights) { + final LightInstance lightInstance = new LightInstance(this, light); + this.lights.add(lightInstance); + this.initNode(this.nodes, this.nodes[nodeIndex++], light, lightInstance); + } + + for (final Helper helper : model.helpers) { + this.initNode(this.nodes, this.nodes[nodeIndex++], helper); + } + + for (final Attachment attachment : model.attachments) { + AttachmentInstance attachmentInstance = null; + + // Attachments may have game models attached to them, such as Undead and + // Nightelf building animations. + if (attachment.internalModel != null) { + attachmentInstance = new AttachmentInstance(this, attachment); + + this.attachments.add(attachmentInstance); + } + + this.initNode(this.nodes, this.nodes[nodeIndex++], attachment, attachmentInstance); + } + + for (final ParticleEmitterObject emitterObject : model.particleEmitters) { + final ParticleEmitter emitter = new ParticleEmitter(this, emitterObject); + + this.particleEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final ParticleEmitter2Object emitterObject : model.particleEmitters2) { + final ParticleEmitter2 emitter = new ParticleEmitter2(this, emitterObject); + + this.particleEmitters2.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final RibbonEmitterObject emitterObject : model.ribbonEmitters) { + final RibbonEmitter emitter = new RibbonEmitter(this, emitterObject); + + this.ribbonEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final EventObjectEmitterObject emitterObject : model.eventObjects) { + final String type = emitterObject.type; + EventObjectEmitter emitter; + + if ("SPN".equals(type)) { + emitter = new EventObjectSpnEmitter(this, emitterObject); + } + else if ("SPL".equals(type)) { + emitter = new EventObjectSplEmitter(this, emitterObject); + } + else if ("UBR".equals(type)) { + emitter = new EventObjectUbrEmitter(this, emitterObject); + } + else { + emitter = new EventObjectSndEmitter(this, emitterObject); + } + + this.eventObjectEmitters.add(emitter); + + this.initNode(this.nodes, this.nodes[nodeIndex++], emitterObject, emitter); + } + + for (final CollisionShape collisionShape : model.collisionShapes) { + this.initNode(this.nodes, this.nodes[nodeIndex++], collisionShape); + } + + // Save a sorted array of all of the nodes, such that every child node comes + // after its parent. + // This allows for flat iteration when updating. + final List hierarchy = model.hierarchy; + + this.sortedNodes = new SkeletalNode[nodes.size()]; + for (int i = 0, l = nodes.size(); i < l; i++) { + this.sortedNodes[i] = this.nodes[hierarchy.get(i)]; + } + + // If the sequence was changed before the model was loaded, reset it now that + // the model loaded. + this.setSequence(this.sequence); + + if (model.bones.size() != 0) { + this.boneTexture = new DataTexture(model.viewer.gl, 4, model.bones.size() * 4, 1); + } + } + + /* + * Clear all of the emitted objects that belong to this instance. + */ + @Override + public void clearEmittedObjects() { + for (final ParticleEmitter emitter : this.particleEmitters) { + emitter.clear(); + } + + for (final ParticleEmitter2 emitter : this.particleEmitters2) { + emitter.clear(); + } + + for (final RibbonEmitter emitter : this.ribbonEmitters) { + emitter.clear(); + } + + for (final EventObjectEmitter emitter : this.eventObjectEmitters) { + emitter.clear(); + } + } + + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject) { + initNode(nodes, node, genericObject, null); + } + + /** + * Initialize a skeletal node. + */ + private void initNode(final MdxNode[] nodes, final SkeletalNode node, final GenericObject genericObject, + final UpdatableObject object) { + node.pivot.set(genericObject.pivot); + + if (genericObject.parentId == -1) { + node.parent = this; + } + else { + node.parent = nodes[genericObject.parentId]; + } + + /// TODO: single-axis billboarding + if (genericObject.billboarded != 0) { + node.billboarded = true; + } + else if (genericObject.billboardedX != 0) { + node.billboardedX = true; + } + else if (genericObject.billboardedY != 0) { + node.billboardedY = true; + } + else if (genericObject.billboardedZ != 0) { + node.billboardedZ = true; + } + + if (object != null) { + node.object = object; + } + + } + + /* + * Overriden to hide also attachment models. + */ + @Override + public void hide() { + super.hide(); + + for (final AttachmentInstance attachment : this.attachments) { + attachment.internalInstance.hide(); + } + } + + /** + * Updates all of this instance internal nodes and objects. Nodes that are + * determined to not be visible will not be updated, nor will any of their + * children down the hierarchy. + */ + public void updateNodes(final float dt, final boolean forced) { + if (!this.model.ok) { + return; + } + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final SkeletalNode[] sortedNodes = this.sortedNodes; + final MdxModel model = (MdxModel) this.model; + final List sortedGenericObjects = model.sortedGenericObjects; + final Scene scene = this.scene; + + // Update the nodes + for (int i = 0, l = sortedNodes.length; i < l; i++) { + final GenericObject genericObject = sortedGenericObjects.get(i); + final SkeletalNode node = sortedNodes[i]; + final GenericNode parent = node.parent; + + genericObject.getVisibility(visibilityHeap, sequence, frame, counter); + + final boolean objectVisible = visibilityHeap[0] > 0; + final boolean nodeVisible = forced || (parent.visible && objectVisible); + + node.visible = nodeVisible; + + // Every node only needs to be updated if this is a forced update, or if both + // the parent node and the generic object corresponding to this node are + // visible. + // Incoming messy code for optimizations! + if (nodeVisible) { + boolean wasDirty = false; + final GenericObject.Variants variants = genericObject.variants; + final Vector3 localLocation = node.localLocation; + final Quaternion localRotation = node.localRotation; + final Vector3 localScale = node.localScale; + + // Only update the local node data if there is a need to + if (forced || variants.generic[sequence]) { + wasDirty = true; + + // Translation + if (forced || variants.translation[sequence]) { + genericObject.getTranslation(translationHeap, sequence, frame, counter); + + localLocation.x = translationHeap[0]; + localLocation.y = translationHeap[1]; + localLocation.z = translationHeap[2]; + } + + // Rotation + if (forced || variants.rotation[sequence]) { + genericObject.getRotation(rotationHeap, sequence, frame, counter); + + localRotation.x = rotationHeap[0]; + localRotation.y = rotationHeap[1]; + localRotation.z = rotationHeap[2]; + localRotation.w = rotationHeap[3]; + } + + // Scale + if (forced || variants.scale[sequence]) { + genericObject.getScale(scaleHeap, sequence, frame, counter); + + localScale.x = scaleHeap[0]; + localScale.y = scaleHeap[1]; + localScale.z = scaleHeap[2]; + } + } + + final boolean wasReallyDirty = forced || wasDirty || parent.wasDirty || genericObject.anyBillboarding; + + node.wasDirty = wasReallyDirty; + + // If this is a forced update, or this node's local data was updated, or the + // parent node was updated, do a full world update. + if (wasReallyDirty) { + node.recalculateTransformation(scene, this.blendTimeRemaining / this.blendTime); + } + + // If there is an instance object associated with this node, and the node is + // visible (which might not be the case for a forced update!), update the + // object. + // This includes attachments and emitters. + final UpdatableObject object = node.object; + + if (object != null) { + object.update(dt, objectVisible); + } + + // Update all of the node's non-skeletal children, which will update their + // children, and so on. + node.updateChildren(dt, scene); + } + } + } + + /** + * Update the batch data. + */ + public void updateBatches(final boolean forced) { + final int sequence = this.sequence; + final int frame = this.frame; + final int counter = this.counter; + final MdxModel model = (MdxModel) this.model; + if (!model.ok) { + return; + } + final List geosets = model.geosets; + final List layers = model.layers; + final float[][] geosetColors = this.geosetColors; + final float[] layerAlphas = this.layerAlphas; + final int[] layerTextures = this.layerTextures; + final float[][] uvAnims = this.uvAnims; + + // Geoset + for (int i = 0, l = geosets.size(); i < l; i++) { + final Geoset geoset = geosets.get(i); + final GeosetAnimation geosetAnimation = geoset.geosetAnimation; + final float[] geosetColor = geosetColors[i]; + + if (geosetAnimation != null) { + // Color + if (forced || (geosetAnimation.variants.get("color")[sequence] != 0)) { + geosetAnimation.getColor(colorHeap, sequence, frame, counter); + + geosetColor[0] = colorHeap[0]; + geosetColor[1] = colorHeap[1]; + geosetColor[2] = colorHeap[2]; + } + + // Alpha + if (forced || (geosetAnimation.variants.get("alpha")[sequence] != 0)) { + geosetAnimation.getAlpha(alphaHeap, sequence, frame, counter); + + geosetColor[3] = alphaHeap[0]; + } + } + else if (forced) { + geosetColor[0] = 1; + geosetColor[1] = 1; + geosetColor[2] = 1; + geosetColor[3] = 1; + } + } + + // Layers + for (int i = 0, l = layers.size(); i < l; i++) { + final Layer layer = layers.get(i); + final TextureAnimation textureAnimation = layer.textureAnimation; + final float[] uvAnim = uvAnims[i]; + + // Alpha + if (forced || (layer.variants.get("alpha")[sequence] != 0)) { + layer.getAlpha(alphaHeap, sequence, frame, counter); + + layerAlphas[i] = alphaHeap[0]; + } + + // Sprite animation + if (forced || (layer.variants.get("textureId")[sequence] != 0)) { + layer.getTextureId(textureIdHeap, sequence, frame, counter); + + layerTextures[i] = (int) textureIdHeap[0]; + } + + if (textureAnimation != null) { + // UV translation animation + if (forced || (textureAnimation.variants.get("translation")[sequence] != 0)) { + textureAnimation.getTranslation(translationHeap, sequence, frame, counter); + + uvAnim[0] = translationHeap[0]; + uvAnim[1] = translationHeap[1]; + } + + // UV rotation animation + if (forced || (textureAnimation.variants.get("rotation")[sequence] != 0)) { + textureAnimation.getRotation(rotationHeap, sequence, frame, counter); + + uvAnim[2] = rotationHeap[2]; + uvAnim[3] = rotationHeap[3]; + } + + // UV scale animation + if (forced || (textureAnimation.variants.get("scale")[sequence] != 0)) { + textureAnimation.getScale(scaleHeap, sequence, frame, counter); + + uvAnim[4] = scaleHeap[0]; + } + } + else if (forced) { + uvAnim[0] = 0; + uvAnim[1] = 0; + uvAnim[2] = 0; + uvAnim[3] = 1; + uvAnim[4] = 1; + } + } + } + + public void updateBoneTexture() { + if (this.boneTexture != null) { + this.worldMatricesCopyHeap.clear(); + for (int i = 0, l = this.worldMatrices.length; i < l; i++) { + final Matrix4 worldMatrix = this.worldMatrices[i]; + this.worldMatricesCopyHeap.put((i * 16) + 0, worldMatrix.val[Matrix4.M00]); + this.worldMatricesCopyHeap.put((i * 16) + 1, worldMatrix.val[Matrix4.M10]); + this.worldMatricesCopyHeap.put((i * 16) + 2, worldMatrix.val[Matrix4.M20]); + this.worldMatricesCopyHeap.put((i * 16) + 3, worldMatrix.val[Matrix4.M30]); + this.worldMatricesCopyHeap.put((i * 16) + 4, worldMatrix.val[Matrix4.M01]); + this.worldMatricesCopyHeap.put((i * 16) + 5, worldMatrix.val[Matrix4.M11]); + this.worldMatricesCopyHeap.put((i * 16) + 6, worldMatrix.val[Matrix4.M21]); + this.worldMatricesCopyHeap.put((i * 16) + 7, worldMatrix.val[Matrix4.M31]); + this.worldMatricesCopyHeap.put((i * 16) + 8, worldMatrix.val[Matrix4.M02]); + this.worldMatricesCopyHeap.put((i * 16) + 9, worldMatrix.val[Matrix4.M12]); + this.worldMatricesCopyHeap.put((i * 16) + 10, worldMatrix.val[Matrix4.M22]); + this.worldMatricesCopyHeap.put((i * 16) + 11, worldMatrix.val[Matrix4.M32]); + this.worldMatricesCopyHeap.put((i * 16) + 12, worldMatrix.val[Matrix4.M03]); + this.worldMatricesCopyHeap.put((i * 16) + 13, worldMatrix.val[Matrix4.M13]); + this.worldMatricesCopyHeap.put((i * 16) + 14, worldMatrix.val[Matrix4.M23]); + this.worldMatricesCopyHeap.put((i * 16) + 15, worldMatrix.val[Matrix4.M33]); + } + this.boneTexture.bindAndUpdate(this.worldMatricesCopyHeap); + } + } + + @Override + public void renderOpaque(final Matrix4 mvp) { + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.opaqueGroups) { + group.render(this, mvp); + } + } + + @Override + public void renderTranslucent() { + if (DynamicShadowManager.IS_SHADOW_MAPPING) { + return; + } + final MdxModel model = (MdxModel) this.model; + + for (final GenericGroup group : model.translucentGroups) { + group.render(this, this.scene.camera.viewProjectionMatrix); + } + } + + @Override + public void updateAnimations(final float dt) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + + if (sequenceId != -1) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + final float frameTime = (dt * 1000 * this.animationSpeed); + + final int lastIntegerFrame = this.frame; + this.floatingFrame += frameTime; + this.blendTimeRemaining -= frameTime; + this.frame = (int) this.floatingFrame; + final int integerFrameTime = this.frame - lastIntegerFrame; + this.counter += integerFrameTime; + this.allowParticleSpawn = true; + + if (this.floatingFrame >= interval[1]) { + if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP) + || ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) { + this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast + + this.resetEventEmitters(); + } + else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation + // mode + final float framesPast = this.floatingFrame - interval[1]; + + final List sequences = model.sequences; + this.sequence = (this.sequence + 1) % sequences.size(); + this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast + this.frame = (int) this.floatingFrame; + this.sequenceEnded = false; + this.resetEventEmitters(); + this.forced = true; + } + else { + this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast + this.counter -= integerFrameTime; + this.allowParticleSpawn = false; + } + if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) { + hide(); + } + + this.sequenceEnded = true; + } + else { + this.sequenceEnded = false; + } + } + + final boolean forced = this.forced; + + if (sequenceId == -1) { + if (forced) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + + // Update the batches + this.updateBatches(forced); + } + } + else { + // let variants = model.variants; + + // if (forced || variants.nodes[sequenceId]) { + // Update the nodes + this.updateNodes(dt, forced); + + this.updateBoneTexture(); + // } + + // if (forced || variants.batches[sequenceId]) { + // Update the batches + this.updateBatches(forced); + // } + } + + this.forced = false; + + } + + @Override + protected void updateLights(final Scene scene) { + for (final LightInstance light : this.lights) { + light.update(scene); + } + } + + @Override + protected void removeLights(final Scene scene2) { + for (final LightInstance light : this.lights) { + light.remove(this.scene); + } + } + + /** + * Set the team color of this instance. + */ + public MdxComplexInstance setTeamColor(final int id) { + this.replaceableTextures[1] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + this.replaceableTextures[2] = (Texture) this.model.viewer.load( + "ReplaceableTextures\\" + ReplaceableIds.getPathString(2) + ReplaceableIds.getIdString(id) + ".blp", + PathSolver.DEFAULT, null); + return this; + } + + @Override + public void setReplaceableTexture(final int replaceableTextureId, final String replaceableTextureFile) { + this.replaceableTextures[replaceableTextureId] = (Texture) this.model.viewer.load(replaceableTextureFile, + PathSolver.DEFAULT, null); + } + + /** + * Set the vertex color of this instance. + */ + public MdxComplexInstance setVertexColor(final float[] color) { + System.arraycopy(color, 0, this.vertexColor, 0, color.length); + + return this; + } + + /** + * Set the sequence of this instance. + */ + public MdxComplexInstance setSequence(final int id) { + final MdxModel model = (MdxModel) this.model; + + final int lastSequence = this.sequence; + this.sequence = id; + + if (model.ok) { + final List sequences = model.sequences; + + if ((id < 0) || (id > (sequences.size() - 1))) { + this.sequence = -1; + this.frame = 0; + this.floatingFrame = 0; + this.allowParticleSpawn = false; + } + else { + if ((this.blendTime > 0) && (lastSequence != this.sequence)) { + this.blendTimeRemaining = this.blendTime; + for (int i = 0, l = this.sortedNodes.length; i < l; i++) { + final SkeletalNode node = this.sortedNodes[i]; + node.beginBlending(); + } + } + + this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast + this.floatingFrame = this.frame; + this.sequenceEnded = false; + } + + this.resetEventEmitters(); + + this.forced = true; + } + + return this; + } + + /** + * Set the seuqnece loop mode. 0 to never loop, 1 to loop based on the model, + * and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay + * spawned effects + */ + public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) { + this.sequenceLoopMode = mode; + + return this; + } + + /** + * Get an attachment node. + */ + public MdxNode getAttachment(final int id) { + final MdxModel model = (MdxModel) this.model; + final Attachment attachment = model.attachments.get(id); + + if (attachment != null) { + return this.nodes[attachment.index]; + } + + return null; + } + + /** + * Event emitters depend on keyframe index changes to emit, rather than only + * values. To work, they need to check what the last keyframe was, and only if + * it's a different one, do something. When changing sequences, these states + * need to be reset, so they can immediately emit things if needed. + */ + private void resetEventEmitters() { + /// TODO: Update this. Said Ghostwolf. + for (final EventObjectEmitter eventObjectEmitter : this.eventObjectEmitters) { + eventObjectEmitter.reset(); + } + } + + @Override + protected RenderBatch getBatch(final TextureMapper textureMapper2) { + throw new UnsupportedOperationException("NOT API"); + } + + public void intersectRayBounds(final Ray ray, final Vector3 intersection) { + this.model.bounds.intersectRay(ray, intersection); + } + + /** + * Intersects a world ray with the model's CollisionShapes. Only ever call this + * function on the Gdx thread because it uses static variables to hold state + * while processing. + * + * @param ray + */ + public boolean intersectRayWithCollision(final Ray ray, final Vector3 intersection, final boolean alwaysUseMesh, + final boolean onlyUseMesh) { + final MdxModel mdxModel = (MdxModel) this.model; + final List collisionShapes = mdxModel.collisionShapes; + if (!onlyUseMesh) { + for (final CollisionShape collisionShape : collisionShapes) { + final MdxNode mdxNode = this.nodes[collisionShape.index]; + if (collisionShape.checkIntersect(ray, mdxNode, intersection)) { + return true; + } + } + } + if (collisionShapes.isEmpty() || alwaysUseMesh) { + for (final Geoset geoset : mdxModel.geosets) { + if (!geoset.unselectable) { + geoset.getAlpha(alphaHeap, this.sequence, this.frame, this.counter); + if (alphaHeap[0] > 0) { + final com.etheller.warsmash.parsers.mdlx.Geoset mdlxGeoset = geoset.mdlxGeoset; + if (CollisionShape.intersectRayTriangles(ray, this, mdlxGeoset.getVertices(), + mdlxGeoset.getFaces(), 3, intersection)) { + return true; + } + } + } + } + } + return false; + } + + public void setAnimationSpeed(final float speedRatio) { + this.animationSpeed = speedRatio; + } + + public void setBlendTime(final float blendTime) { + this.blendTime = blendTime; + } + + public void setFrame(final int frame) { + this.frame = frame; + this.floatingFrame = frame; + } + + public void setFrameByRatio(final float ratioOfAnimationCompleted) { + if (this.sequence != -1) { + final Sequence currentlyPlayingSequence = ((MdxModel) this.model).sequences.get(this.sequence); + this.floatingFrame = currentlyPlayingSequence.getInterval()[0] + + ((currentlyPlayingSequence.getInterval()[1] - currentlyPlayingSequence.getInterval()[0]) + * ratioOfAnimationCompleted); + this.frame = (int) this.floatingFrame; + } + } } 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 7f8db03..82ab54f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -86,16 +86,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitFilterFunction; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; import mpq.MPQArchive; @@ -1275,74 +1268,6 @@ public class War3MapViewer extends ModelViewer { return (numElements < 0) ? 1 : (numElements >= MAXIMUM_ACCEPTED) ? MAXIMUM_ACCEPTED : numElements + 1; } - public boolean orderSmart(final float x, final float y) { - mousePosHeap.x = x; - mousePosHeap.y = y; - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityMove) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - PointAbilityTargetCheckReceiver.INSTANCE); - final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (target != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, mousePosHeap, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } - - } - return ordered; - } - - public boolean orderSmart(final RenderUnit target) { - boolean ordered = false; - for (final RenderUnit unit : this.selected) { - for (final CAbility ability : unit.getSimulationUnit().getAbilities()) { - if (ability instanceof CAbilityAttack) { - ability.checkCanUse(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - BooleanAbilityActivationReceiver.INSTANCE); - if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { - ability.checkCanTarget(this.simulation, unit.getSimulationUnit(), OrderIds.smart, - target.getSimulationUnit(), CWidgetAbilityTargetCheckReceiver.INSTANCE); - final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget(); - if (targetWidget != null) { - ability.onOrder(this.simulation, unit.getSimulationUnit(), OrderIds.smart, targetWidget, - false); - ordered = true; - } - else { - System.err.println("Target not valid."); - } - } - else { - System.err.println("Ability not ok to use."); - } - } - else { - System.err.println("Ability not move."); - } - } - - } - return ordered; - } - public void standOnRepeat(final MdxComplexInstance instance) { instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP); SequenceUtils.randomStandSequence(instance); 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 d0c12de..12704ea 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 @@ -38,6 +38,7 @@ public class RenderUnit { private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori"); private static final War3ID ANIM_PROPS = War3ID.fromString("uani"); + private static final War3ID BLEND_TIME = War3ID.fromString("uble"); private static final float[] heapZ = new float[3]; public final MdxComplexInstance instance; public final MutableGameObject row; @@ -124,6 +125,9 @@ public class RenderUnit { orientationInterpolationOrdinal = 0; } this.orientationInterpolation = OrientationInterpolation.VALUES[orientationInterpolationOrdinal]; + + final float blendTime = row.getFieldAsFloat(BLEND_TIME, 0); + instance.setBlendTime(blendTime * 1000.0f); } this.instance = instance; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 9971f88..d43f106 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -35,5 +35,5 @@ public interface CommandButtonListener { // // int getOrderId(); void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, - boolean active); + int autoCastOrderId, boolean active, boolean autoCastActive); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index 014d80b..eac054d 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -6,10 +6,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor { public static final CommandCardPopulatingAbilityVisitor INSTANCE = new CommandCardPopulatingAbilityVisitor(); @@ -33,41 +34,54 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor units; private final List players; private final List projectiles; @@ -47,6 +48,7 @@ public class CSimulation { private float currentGameDayTimeElapsed; private final Map handleIdToUnit = new HashMap<>(); private final Map handleIdToAbility = new HashMap<>(); + private transient CommandErrorListener commandErrorListener; public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController, @@ -55,8 +57,8 @@ public class CSimulation { this.gameplayConstants = new CGameplayConstants(miscData); this.simulationRenderController = simulationRenderController; this.pathingGrid = pathingGrid; - this.unitData = new CUnitData(parsedUnitData); this.abilityData = new CAbilityData(parsedAbilityData); + this.unitData = new CUnitData(parsedUnitData, this.abilityData); this.units = new ArrayList<>(); this.projectiles = new ArrayList<>(); this.newProjectiles = new ArrayList<>(); @@ -92,6 +94,13 @@ public class CSimulation { neutralPassive.setAlliance(cPlayer, CAllianceType.PASSIVE, true); } + this.commandErrorListener = new CommandErrorListener() { + @Override + public void showCommandError(final String message) { + throw new RuntimeException(message); + } + }; + } public CUnitData getUnitData() { @@ -208,4 +217,8 @@ public class CSimulation { public CPlayer getPlayer(final int index) { return this.players.get(index); } + + public CommandErrorListener getCommandErrorListener() { + return this.commandErrorListener; + } } 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 2efda2b..75de310 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 @@ -14,11 +14,16 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorStop; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrder; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; @@ -38,8 +43,9 @@ public class CUnit extends CWidget { private final List abilities = new ArrayList<>(); - private CBehavior currentOrder; - private final Queue orderQueue = new LinkedList<>(); + private CBehavior currentBehavior; + private COrder currentOrder; + private final Queue orderQueue = new LinkedList<>(); private final CUnitType unitType; private Rectangle collisionRectangle; @@ -59,6 +65,12 @@ public class CUnit extends CWidget { private final float acquisitionRange; private transient static AutoAttackTargetFinderEnum autoAttackTargetFinderEnum = new AutoAttackTargetFinderEnum(); + private transient CBehaviorMove moveBehavior; + private transient CBehaviorAttack attackBehavior; + private transient CBehaviorFollow followBehavior; + private transient CBehaviorPatrol patrolBehavior; + private transient CBehaviorStop stopBehavior; + 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 int defense, final CUnitType unitType) { @@ -75,6 +87,8 @@ public class CUnit extends CWidget { this.unitType = unitType; this.classifications.addAll(unitType.getClassifications()); this.acquisitionRange = unitType.getDefaultAcquisitionRange(); + this.stopBehavior = new CBehaviorStop(this); + this.currentBehavior = this.stopBehavior; } public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) { @@ -185,38 +199,33 @@ public class CUnit extends CWidget { return true; } } - else if (this.currentOrder != null) { - if (this.currentOrder.update(game)) { - // remove current order, because it's completed, polling next - // item from order queue - this.currentOrder = this.orderQueue.poll(); - this.stateNotifier.ordersChanged(); - } - if (this.currentOrder == null) { - // maybe order "stop" here - this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); - } + else if (this.currentBehavior != null) { + this.currentBehavior = this.currentBehavior.update(game); } else { // check to auto acquire targets - if (!this.unitType.getAttacks().isEmpty()) { - if (this.collisionRectangle != null) { - tempRect.set(this.collisionRectangle); - } - else { - tempRect.set(this.getX(), this.getY(), 0, 0); - } - final float halfSize = this.acquisitionRange; - tempRect.x -= halfSize; - tempRect.y -= halfSize; - tempRect.width += halfSize * 2; - tempRect.height += halfSize * 2; - game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); - } + autoAcquireAttackTargets(game); } return false; } + public void autoAcquireAttackTargets(final CSimulation game) { + if (!this.unitType.getAttacks().isEmpty()) { + if (this.collisionRectangle != null) { + tempRect.set(this.collisionRectangle); + } + else { + tempRect.set(this.getX(), this.getY(), 0, 0); + } + final float halfSize = this.acquisitionRange; + tempRect.x -= halfSize; + tempRect.y -= halfSize; + tempRect.width += halfSize * 2; + tempRect.height += halfSize * 2; + game.getWorldCollision().enumUnitsInRect(tempRect, autoAttackTargetFinderEnum.reset(game, this)); + } + } + public float getEndingDecayTime(final CSimulation game) { if (this.unitType.isBuilding()) { return game.getGameplayConstants().getStructureDecayTime(); @@ -224,7 +233,7 @@ public class CUnit extends CWidget { return game.getGameplayConstants().getBoneDecayTime(); } - public void order(final CBehavior order, final boolean queue) { + public void order(final CSimulation game, final COrder order, final boolean queue) { if (isDead()) { return; } @@ -232,14 +241,37 @@ public class CUnit extends CWidget { this.orderQueue.add(order); } else { - this.currentOrder = order; + this.currentBehavior = beginOrder(game, order); this.orderQueue.clear(); } - this.stateNotifier.ordersChanged(); } - public CBehavior getCurrentOrder() { - return this.currentOrder; + private CBehavior beginOrder(final CSimulation game, final COrder order) { + final boolean omitNotify = (this.currentOrder == null) && (order == null); + this.currentOrder = order; + if (!omitNotify) { + this.stateNotifier.ordersChanged(getCurrentAbilityHandleId(), getCurrentOrderId()); + } + CBehavior nextBehavior; + if (order != null) { + nextBehavior = order.begin(game, this); + } + else { + nextBehavior = this.stopBehavior; + } + return nextBehavior; + } + + public CBehavior getCurrentBehavior() { + return this.currentBehavior; + } + + public int getCurrentAbilityHandleId() { + return this.currentOrder == null ? 0 : this.currentOrder.getAbilityHandleId(); + } + + public int getCurrentOrderId() { + return this.currentOrder == null ? OrderIds.stop : this.currentOrder.getOrderId(); } public List getAbilities() { @@ -389,12 +421,12 @@ public class CUnit extends CWidget { } } else { - if (this.currentOrder == null) { + if (this.currentBehavior == null) { if (!simulation.getPlayer(getPlayerIndex()).hasAlliance(source.getPlayerIndex(), CAllianceType.PASSIVE)) { for (final CUnitAttack attack : this.unitType.getAttacks()) { if (source.canBeTargetedBy(simulation, this, attack.getTargetsAllowed())) { - this.order(new CBehaviorAttack(this, attack, OrderIds.attack, source), false); + this.currentBehavior = getAttackBehavior().reset(attack, source); break; } } @@ -404,7 +436,7 @@ public class CUnit extends CWidget { } private void kill(final CSimulation simulation) { - this.currentOrder = null; + this.currentBehavior = null; this.orderQueue.clear(); this.deathTurnTick = simulation.getGameTurnTick(); } @@ -558,7 +590,7 @@ public class CUnit extends CWidget { if (this.source.canReach(unit, this.source.acquisitionRange) && unit.canBeTargetedBy(this.game, this.source, attack.getTargetsAllowed()) && (this.source.distance(unit) >= this.source.getUnitType().getMinimumAttackRange())) { - this.source.order(new CBehaviorAttack(this.source, attack, OrderIds.attack, unit), false); + this.source.currentBehavior = this.source.getAttackBehavior().reset(attack, unit); return true; } } @@ -566,4 +598,45 @@ public class CUnit extends CWidget { return false; } } + + public CBehaviorMove getMoveBehavior() { + return this.moveBehavior; + } + + public void setMoveBehavior(final CBehaviorMove moveBehavior) { + this.moveBehavior = moveBehavior; + } + + public CBehaviorAttack getAttackBehavior() { + return this.attackBehavior; + } + + public void setAttackBehavior(final CBehaviorAttack attackBehavior) { + this.attackBehavior = attackBehavior; + } + + public CBehaviorStop getStopBehavior() { + return this.stopBehavior; + } + + public void setFollowBehavior(final CBehaviorFollow followBehavior) { + this.followBehavior = followBehavior; + } + + public void setPatrolBehavior(final CBehaviorPatrol patrolBehavior) { + this.patrolBehavior = patrolBehavior; + } + + public CBehaviorFollow getFollowBehavior() { + return this.followBehavior; + } + + public CBehaviorPatrol getPatrolBehavior() { + return this.patrolBehavior; + } + + public CBehavior pollNextOrderBehavior(final CSimulation game) { + final COrder order = this.orderQueue.poll(); + return beginOrder(game, order); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index c1d3f51..94b974c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -5,7 +5,7 @@ import com.etheller.warsmash.util.SubscriberSetNotifier; public interface CUnitStateListener { void lifeChanged(); // hp (current) changes - void ordersChanged(); + void ordersChanged(int abilityHandleId, int orderId); public static final class CUnitStateNotifier extends SubscriberSetNotifier implements CUnitStateListener { @@ -17,9 +17,9 @@ public interface CUnitStateListener { } @Override - public void ordersChanged() { + public void ordersChanged(final int abilityHandleId, final int orderId) { for (final CUnitStateListener listener : set) { - listener.ordersChanged(); + listener.ordersChanged(abilityHandleId, orderId); } } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java index 743def9..7720194 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbility.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; public interface CAbility extends CAbilityView { /* should fire when ability added to unit */ @@ -12,10 +13,10 @@ public interface CAbility extends CAbilityView { /* should fire when ability removed from unit */ void onRemove(CSimulation game, CUnit unit); - void onOrder(CSimulation game, CUnit caster, int orderId, CWidget target, boolean queue); + CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target); - void onOrder(CSimulation game, CUnit caster, int orderId, Vector2 point, boolean queue); + CBehavior begin(CSimulation game, CUnit caster, int orderId, Vector2 point); - void onOrderNoTarget(CSimulation game, CUnit caster, int orderId, boolean queue); + CBehavior beginNoTarget(CSimulation game, CUnit caster, int orderId); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java index c85cb66..f7e57d8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityAttack.java @@ -4,12 +4,10 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorAttack; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -99,6 +97,7 @@ public class CAbilityAttack implements CAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { + unit.setAttackBehavior(new CBehaviorAttack(unit)); } @Override @@ -106,30 +105,28 @@ public class CAbilityAttack implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, - final boolean queue) { - CBehavior order = null; + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + CBehavior behavior = null; for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { - order = new CBehaviorAttack(caster, attack, orderId, target); + behavior = caster.getAttackBehavior().reset(attack, target); break; } } - if (order == null) { - order = new CBehaviorMove(caster, orderId, target.getX(), target.getY()); + if (behavior == null) { + behavior = caster.getMoveBehavior().reset(target.getX(), target.getY()); } - caster.order(order, queue); + return behavior; } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, - final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_WIDGET); + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java new file mode 100644 index 0000000..5efb110 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityColdArrows.java @@ -0,0 +1,117 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +/** + * Represents an ability from the object data + */ +public class CAbilityColdArrows implements CAbility { + private final War3ID rawcode; + private final int handleId; + private boolean autoCastActive; + + public CAbilityColdArrows(final War3ID rawcode, final int handleId) { + this.rawcode = rawcode; + this.handleId = handleId; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.coldarrowstarg: + receiver.targetOk(target); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + switch (orderId) { + case OrderIds.coldarrows: + case OrderIds.uncoldarrows: + receiver.targetOk(null); + break; + default: + receiver.orderIdNotAccepted(); + break; + } + } + + public War3ID getRawcode() { + return this.rawcode; + } + + @Override + public int getHandleId() { + return this.handleId; + } + + public boolean isAutoCastActive() { + return this.autoCastActive; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + CBehavior behavior = null; + for (final CUnitAttack attack : caster.getUnitType().getAttacks()) { + if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) { + behavior = caster.getAttackBehavior().reset(attack, target); + break; + } + } + if (behavior != null) { + return behavior; + } + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + this.autoCastActive = !this.autoCastActive; + return caster.pollNextOrderBehavior(game); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java new file mode 100644 index 0000000..40ff01c --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java @@ -0,0 +1,84 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; + +/** + * Represents an ability from the object data + */ +public class CAbilityGeneric implements CAbility { + private final War3ID rawcode; + private final int handleId; + + public CAbilityGeneric(final War3ID rawcode, final int handleId) { + this.rawcode = rawcode; + this.handleId = handleId; + } + + public War3ID getRawcode() { + return this.rawcode; + } + + @Override + public void checkCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.notAnActiveAbility(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final CWidget target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTarget(final CSimulation game, final CUnit unit, final int orderId, final Vector2 target, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + public int getHandleId() { + return this.handleId; + } + + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + return caster.pollNextOrderBehavior(game); + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java index 19d1765..8c34b8e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityMove.java @@ -4,9 +4,10 @@ import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorMove; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CBehaviorPatrol; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorFollow; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorMove; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehaviorPatrol; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; @@ -31,7 +32,7 @@ public class CAbilityMove implements CAbility { switch (orderId) { case OrderIds.smart: case OrderIds.patrol: - if (target instanceof CUnit) { + if ((target instanceof CUnit) && (target != unit)) { receiver.targetOk(target); } else { @@ -74,7 +75,9 @@ public class CAbilityMove implements CAbility { @Override public void onAdd(final CSimulation game, final CUnit unit) { - + unit.setMoveBehavior(new CBehaviorMove(unit)); + unit.setFollowBehavior(new CBehaviorFollow(unit)); + unit.setPatrolBehavior(new CBehaviorPatrol(unit)); } @Override @@ -83,20 +86,23 @@ public class CAbilityMove implements CAbility { } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final CWidget target, - final boolean queue) { - caster.order(new CBehaviorPatrol(caster, orderId, (CUnit) target), queue); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return caster.getFollowBehavior().reset((CUnit) target); } @Override - public void onOrder(final CSimulation game, final CUnit caster, final int orderId, final Vector2 target, - final boolean queue) { - caster.order(new CBehaviorMove(caster, orderId, target.x, target.y), queue); + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final Vector2 point) { + if (orderId == OrderIds.patrol) { + return caster.getPatrolBehavior().reset(point); + } + else { + return caster.getMoveBehavior().reset(point.x, point.y); + } } @Override - public void onOrderNoTarget(final CSimulation game, final CUnit caster, final int orderId, final boolean queue) { - throw new IllegalArgumentException(StringsToExternalizeLater.MUST_TARGET_POINT); + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return caster.pollNextOrderBehavior(game); } @Override diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index 626aec9..5b4d1fb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -12,4 +12,8 @@ public interface CAbilityVisitor { T accept(CAbilityAttack ability); T accept(CAbilityMove ability); + + T accept(CAbilityGeneric ability); + + T accept(CAbilityColdArrows ability); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java new file mode 100644 index 0000000..809d097 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CAbstractRangedBehavior.java @@ -0,0 +1,108 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; + +public abstract class CAbstractRangedBehavior implements CRangedBehavior { + protected final CUnit unit; + + public CAbstractRangedBehavior(final CUnit unit) { + this.unit = unit; + } + + private boolean wasWithinPropWindow = false; + protected CWidget target; + private boolean wasInRange = false; + private CBehaviorMove moveBehavior; + + protected final CAbstractRangedBehavior innerReset(final CWidget target) { + this.wasWithinPropWindow = false; + this.target = target; + this.wasInRange = false; + if (!this.unit.isMovementDisabled()) { + if ((target instanceof CUnit) && !((CUnit) target).getUnitType().isBuilding()) { + this.moveBehavior = this.unit.getMoveBehavior().reset((CUnit) target, this); + } + else { + this.moveBehavior = this.unit.getMoveBehavior().reset(target.getX(), target.getY(), this); + } + } + else { + this.moveBehavior = null; + } + return this; + } + + protected abstract CBehavior update(CSimulation simulation, boolean withinRange); + + protected abstract boolean checkTargetStillValid(CSimulation simulation); + + protected abstract void resetBeforeMoving(CSimulation simulation); + + @Override + public final CBehavior update(final CSimulation simulation) { + if (!checkTargetStillValid(simulation)) { + return this.unit.pollNextOrderBehavior(simulation); + } + if (!isWithinRange(simulation)) { + if (this.moveBehavior == null) { + return this.unit.pollNextOrderBehavior(simulation); + } + this.wasInRange = false; + resetBeforeMoving(simulation); + ; + return this.unit.getMoveBehavior(); + } + this.wasInRange = true; + if (!this.unit.isMovementDisabled()) { + final float prevX = this.unit.getX(); + final float prevY = this.unit.getY(); + final float deltaY = this.target.getY() - prevY; + final float deltaX = this.target.getX() - prevX; + 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.getGameplayConstants().getAttackHalfAngle(); + final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); + + 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) { + this.wasWithinPropWindow = true; + } + else { + // If this happens, the unit is facing the wrong way, and has to turn before + // moving. + this.wasWithinPropWindow = false; + } + } + else { + this.wasWithinPropWindow = true; + } + + return update(simulation, this.wasWithinPropWindow); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java new file mode 100644 index 0000000..f848b67 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehavior.java @@ -0,0 +1,13 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public interface CBehavior { + /** + * Executes one step of game simulation of the current order, and then returns + * the next behavior for the unit after the result of the update cycle. + * + * @return + */ + CBehavior update(CSimulation game); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java new file mode 100644 index 0000000..6004fd5 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorAttack.java @@ -0,0 +1,111 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; + +public class CBehaviorAttack extends CAbstractRangedBehavior { + + public CBehaviorAttack(final CUnit unit) { + super(unit); + } + + private CUnitAttack unitAttack; + private int damagePointLaunchTime; + private int backSwingTime; + private int thisOrderCooldownEndTime; + + public CBehaviorAttack reset(final CUnitAttack unitAttack, final CWidget target) { + super.innerReset(target); + this.unitAttack = unitAttack; + this.damagePointLaunchTime = 0; + this.backSwingTime = 0; + this.thisOrderCooldownEndTime = 0; + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + float range = this.unitAttack.getRange(); + if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentBehavior() instanceof CBehaviorMove) + && (this.damagePointLaunchTime != 0 /* + * only apply range motion buffer if they were already in range and + * attacked + */)) { + range += this.unitAttack.getRangeMotionBuffer(); + } + return this.unit.canReach(this.target, range) + && (this.unit.distance(this.target) >= this.unit.getUnitType().getMinimumAttackRange()); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return !this.target.isDead() + && this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed()); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + this.damagePointLaunchTime = 0; + this.thisOrderCooldownEndTime = 0; + } + + @Override + public CBehavior update(final CSimulation simulation, final boolean withinRange) { + final int cooldownEndTime = this.unit.getCooldownEndTime(); + final int currentTurnTick = simulation.getGameTurnTick(); + if (withinRange) { + if (this.damagePointLaunchTime != 0) { + if (currentTurnTick >= this.damagePointLaunchTime) { + int minDamage = this.unitAttack.getMinDamage(); + final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage()); + if (minDamage > maxDamage) { + minDamage = maxDamage; + } + final int damage; + if (maxDamage == 0) { + damage = 0; + } + else if (minDamage == maxDamage) { + damage = minDamage; + } + else { + damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; + } + this.unitAttack.launch(simulation, this.unit, this.target, damage); + this.damagePointLaunchTime = 0; + } + } + else if (currentTurnTick >= cooldownEndTime) { + final float cooldownTime = this.unitAttack.getCooldownTime(); + final float animationBackswingPoint = this.unitAttack.getAnimationBackswingPoint(); + final int a1CooldownSteps = (int) (cooldownTime / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1BackswingSteps = (int) (animationBackswingPoint / WarsmashConstants.SIMULATION_STEP_TIME); + final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() + / WarsmashConstants.SIMULATION_STEP_TIME); + this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); + this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps; + this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps; + this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps; + this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f, + true); + this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false); + } + else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); + } + } + else { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, + false); + } + + return this; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java new file mode 100644 index 0000000..ee80015 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorFollow.java @@ -0,0 +1,38 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorFollow extends CAbstractRangedBehavior { + + public CBehaviorFollow(final CUnit unit) { + super(unit); + } + + public CBehavior reset(final CUnit target) { + return innerReset(target); + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.canReach(this.target, this.unit.getAcquisitionRange()); + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinRange) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false); + return this; + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return !this.target.isDead(); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java similarity index 87% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java index 2bca116..5171364 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorMove.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorMove.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; import java.awt.geom.Point2D; import java.awt.geom.Point2D.Float; @@ -18,35 +18,58 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindin public class CBehaviorMove implements CBehavior { private static final Rectangle tempRect = new Rectangle(); private final CUnit unit; - private final int orderId; - private boolean wasWithinPropWindow = false; - private List path = null; - private final CPathfindingProcessor.GridMapping gridMapping; - private final Point2D.Float target; - private int searchCycles = 0; - private CUnit followUnit; - public CBehaviorMove(final CUnit unit, final int orderId, final float targetX, final float targetY) { + public CBehaviorMove(final CUnit unit) { this.unit = unit; - this.orderId = orderId; - this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( - unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS - : CPathfindingProcessor.GridMapping.CELLS; - this.target = new Point2D.Float(targetX, targetY); } - public CBehaviorMove(final CUnit unit, final int orderId, final CUnit followUnit) { - this.unit = unit; - this.orderId = orderId; + private boolean wasWithinPropWindow = false; + private List path = null; + private CPathfindingProcessor.GridMapping gridMapping; + private Point2D.Float target; + private int searchCycles = 0; + private CUnit followUnit; + private CRangedBehavior rangedBehavior; + + public CBehaviorMove reset(final float targetX, final float targetY) { + return reset(targetX, targetY, null); + } + + public CBehaviorMove reset(final float targetX, final float targetY, final CRangedBehavior rangedBehavior) { + this.wasWithinPropWindow = false; this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( - unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS + : CPathfindingProcessor.GridMapping.CELLS; + this.target = new Point2D.Float(targetX, targetY); + this.path = null; + this.searchCycles = 0; + this.followUnit = null; + this.rangedBehavior = rangedBehavior; + return this; + } + + public CBehaviorMove reset(final CUnit followUnit) { + return reset(followUnit, null); + } + + public CBehaviorMove reset(final CUnit followUnit, final CRangedBehavior rangedBehavior) { + this.wasWithinPropWindow = false; + this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners( + this.unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS : CPathfindingProcessor.GridMapping.CELLS; this.target = new Point2D.Float(followUnit.getX(), followUnit.getY()); + this.path = null; + this.searchCycles = 0; this.followUnit = followUnit; + this.rangedBehavior = rangedBehavior; + return this; } @Override - public boolean update(final CSimulation simulation) { + public CBehavior update(final CSimulation simulation) { + if ((this.rangedBehavior != null) && this.rangedBehavior.isWithinRange(simulation)) { + return this.rangedBehavior; + } final float prevX = this.unit.getX(); final float prevY = this.unit.getY(); @@ -119,7 +142,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles < 4); System.out.println("new path (for target) " + this.path); if (this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } } float currentTargetX; @@ -217,7 +240,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles = 0; } if (this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } else { System.out.println(this.path); @@ -249,7 +272,7 @@ public class CBehaviorMove implements CBehavior { deltaY = currentTargetY - nextY; deltaX = currentTargetX - nextX; if ((deltaX == 0.000f) && (deltaY == 0.000f) && this.path.isEmpty()) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } System.out.println("new target: " + currentTargetX + "," + currentTargetY); System.out.println("new delta: " + deltaX + "," + deltaY); @@ -274,7 +297,7 @@ public class CBehaviorMove implements CBehavior { SequenceUtils.EMPTY, 1.0f, true); } this.wasWithinPropWindow = false; - return false; + return this; } } } @@ -290,7 +313,7 @@ public class CBehaviorMove implements CBehavior { this.searchCycles++; System.out.println("new path " + this.path); if (this.path.isEmpty() || (this.searchCycles > 5)) { - return true; + return this.unit.pollNextOrderBehavior(simulation); } } this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f, @@ -309,12 +332,7 @@ public class CBehaviorMove implements CBehavior { this.wasWithinPropWindow = false; } - return false; - } - - @Override - public int getOrderId() { - return this.orderId; + return this; } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java new file mode 100644 index 0000000..947fbf3 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorPatrol.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorPatrol implements CRangedBehavior { + + private final CUnit unit; + private Vector2 target; + private Vector2 startPoint; + + public CBehaviorPatrol(final CUnit unit) { + this.unit = unit; + } + + public CBehavior reset(final Vector2 target) { + this.target = target; + this.startPoint = new Vector2(this.unit.getX(), this.unit.getY()); + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + return this.unit.distance(this.target.x, this.target.y) <= simulation.getGameplayConstants() + .getCloseEnoughRange(); // TODO this is not how it was meant to be used + } + + @Override + public CBehavior update(final CSimulation simulation) { + final Vector2 temp = this.target; + this.target = this.startPoint; + this.startPoint = temp; + return this.unit.getMoveBehavior().reset(this.target.x, this.target.y, this); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java new file mode 100644 index 0000000..f884891 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CBehaviorStop.java @@ -0,0 +1,22 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; + +public class CBehaviorStop implements CBehavior { + + private final CUnit unit; + + public CBehaviorStop(final CUnit unit) { + this.unit = unit; + } + + @Override + public CBehavior update(final CSimulation game) { + this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true); + return this.unit.pollNextOrderBehavior(game); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java new file mode 100644 index 0000000..5c1f8a1 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/CRangedBehavior.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; + +public interface CRangedBehavior extends CBehavior { + boolean isWithinRange(final CSimulation simulation); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 7188d3c..987ca6c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -1,27 +1,24 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data; import com.etheller.warsmash.units.manager.MutableObjectData; -import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; -import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; public class CAbilityData { - private static final War3ID ABILITY_ICON = War3ID.fromString("aart"); + private static final War3ID COLD_ARROWS = War3ID.fromString("ACcw"); private final MutableObjectData abilityData; public CAbilityData(final MutableObjectData abilityData) { this.abilityData = abilityData; } - public String getIconPath(final War3ID id, final int level) { - final MutableGameObject mutableGameObject = this.abilityData.get(id); - if (mutableGameObject == null) { - return ImageUtils.DEFAULT_ICON_PATH; + public CAbility createAbility(final String ability, final int handleId) { + final War3ID war3Id = War3ID.fromString(ability); + if (war3Id.equals(COLD_ARROWS)) { + return new CAbilityColdArrows(war3Id, handleId); } - final String iconPath = mutableGameObject.getFieldAsString(ABILITY_ICON, level); - if ((iconPath == null) || "".equals(iconPath)) { - return ImageUtils.DEFAULT_ICON_PATH; - } - return iconPath; + return new CAbilityGeneric(war3Id, handleId); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index c041354..37f184c 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -32,396 +32,410 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUni import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController; public class CUnitData { - private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); - private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); - private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); - private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); - private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); - private static final War3ID TURN_RATE = War3ID.fromString("umvr"); - private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); - private static final War3ID NAME = War3ID.fromString("unam"); - private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); - private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); - private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); - private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); - private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); - private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); - private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); - private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); - private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); - private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); - private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); - private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); - private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); - private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); - private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); - private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); - private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); - private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); - private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); - private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); - private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); - private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); - private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); - private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); - private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); - private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); - private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); - private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); + private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); + private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); + private static final War3ID MOVEMENT_SPEED_BASE = War3ID.fromString("umvs"); + private static final War3ID PROPULSION_WINDOW = War3ID.fromString("uprw"); + private static final War3ID TURN_RATE = War3ID.fromString("umvr"); + private static final War3ID IS_BLDG = War3ID.fromString("ubdg"); + private static final War3ID NAME = War3ID.fromString("unam"); + private static final War3ID PROJECTILE_LAUNCH_X = War3ID.fromString("ulpx"); + private static final War3ID PROJECTILE_LAUNCH_Y = War3ID.fromString("ulpy"); + private static final War3ID PROJECTILE_LAUNCH_Z = War3ID.fromString("ulpz"); + private static final War3ID ATTACKS_ENABLED = War3ID.fromString("uaen"); + private static final War3ID ATTACK1_BACKSWING_POINT = War3ID.fromString("ubs1"); + private static final War3ID ATTACK1_DAMAGE_POINT = War3ID.fromString("udp1"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua1f"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua1h"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua1q"); + private static final War3ID ATTACK1_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua1p"); + private static final War3ID ATTACK1_ATTACK_TYPE = War3ID.fromString("ua1t"); + private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c"); + private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd1"); + private static final War3ID ATTACK1_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd1"); + private static final War3ID ATTACK1_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl1"); + private static final War3ID ATTACK1_DMG_DICE = War3ID.fromString("ua1d"); + private static final War3ID ATTACK1_DMG_SIDES_PER_DIE = War3ID.fromString("ua1s"); + private static final War3ID ATTACK1_DMG_SPILL_DIST = War3ID.fromString("usd1"); + private static final War3ID ATTACK1_DMG_SPILL_RADIUS = War3ID.fromString("usr1"); + private static final War3ID ATTACK1_DMG_UPGRADE_AMT = War3ID.fromString("udu1"); + private static final War3ID ATTACK1_TARGET_COUNT = War3ID.fromString("utc1"); + private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1"); + private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m"); + private static final War3ID ATTACK1_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh1"); + private static final War3ID ATTACK1_PROJECTILE_SPEED = War3ID.fromString("ua1z"); + private static final War3ID ATTACK1_RANGE = War3ID.fromString("ua1r"); + private static final War3ID ATTACK1_RANGE_MOTION_BUFFER = War3ID.fromString("urb1"); + private static final War3ID ATTACK1_SHOW_UI = War3ID.fromString("uwu1"); + private static final War3ID ATTACK1_TARGETS_ALLOWED = War3ID.fromString("ua1g"); + private static final War3ID ATTACK1_WEAPON_SOUND = War3ID.fromString("ucs1"); + private static final War3ID ATTACK1_WEAPON_TYPE = War3ID.fromString("ua1w"); - private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); - private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); - private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); - private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); - private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); - private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); - private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); - private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); - private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); - private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); - private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); - private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); - private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); - private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); - private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); - private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); - private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); - private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); - private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); - private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); - private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); - private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); - private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); - private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID ATTACK2_BACKSWING_POINT = War3ID.fromString("ubs2"); + private static final War3ID ATTACK2_DAMAGE_POINT = War3ID.fromString("udp2"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_FULL_DMG = War3ID.fromString("ua2f"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_HALF_DMG = War3ID.fromString("ua2h"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_QUARTER_DMG = War3ID.fromString("ua2q"); + private static final War3ID ATTACK2_AREA_OF_EFFECT_TARGETS = War3ID.fromString("ua2p"); + private static final War3ID ATTACK2_ATTACK_TYPE = War3ID.fromString("ua2t"); + private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c"); + private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_HALF = War3ID.fromString("uhd2"); + private static final War3ID ATTACK2_DAMAGE_FACTOR_QUARTER = War3ID.fromString("uqd2"); + private static final War3ID ATTACK2_DAMAGE_LOSS_FACTOR = War3ID.fromString("udl2"); + private static final War3ID ATTACK2_DMG_DICE = War3ID.fromString("ua2d"); + private static final War3ID ATTACK2_DMG_SIDES_PER_DIE = War3ID.fromString("ua2s"); + private static final War3ID ATTACK2_DMG_SPILL_DIST = War3ID.fromString("usd2"); + private static final War3ID ATTACK2_DMG_SPILL_RADIUS = War3ID.fromString("usr2"); + private static final War3ID ATTACK2_DMG_UPGRADE_AMT = War3ID.fromString("udu2"); + private static final War3ID ATTACK2_TARGET_COUNT = War3ID.fromString("utc2"); + private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2"); + private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m"); + private static final War3ID ATTACK2_PROJECTILE_HOMING_ENABLED = War3ID.fromString("umh2"); + private static final War3ID ATTACK2_PROJECTILE_SPEED = War3ID.fromString("ua2z"); + private static final War3ID ATTACK2_RANGE = War3ID.fromString("ua2r"); + private static final War3ID ATTACK2_RANGE_MOTION_BUFFER = War3ID.fromString("urb2"); + private static final War3ID ATTACK2_SHOW_UI = War3ID.fromString("uwu2"); + private static final War3ID ATTACK2_TARGETS_ALLOWED = War3ID.fromString("ua2g"); + private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); + private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); - private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); - private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); + private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); - private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); + private static final War3ID PROJECTILE_IMPACT_Z = War3ID.fromString("uimz"); - private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); - private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); + private static final War3ID DEATH_TYPE = War3ID.fromString("udea"); + private static final War3ID ARMOR_TYPE = War3ID.fromString("uarm"); - private static final War3ID DEFENSE = War3ID.fromString("udef"); - private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); - private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); - private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); - private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); - private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); - private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); - private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - private final MutableObjectData unitData; - private final Map unitIdToUnitType = new HashMap<>(); + private static final War3ID DEFENSE = War3ID.fromString("udef"); + private static final War3ID DEFENSE_TYPE = War3ID.fromString("udty"); + private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh"); + private static final War3ID MOVE_TYPE = War3ID.fromString("umvt"); + private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol"); + private static final War3ID CLASSIFICATION = War3ID.fromString("utyp"); + private static final War3ID DEATH_TIME = War3ID.fromString("udtm"); + private static final War3ID TARGETED_AS = War3ID.fromString("utar"); - public CUnitData(final MutableObjectData unitData) { - this.unitData = unitData; - } + private static final War3ID ABILITIES_NORMAL = War3ID.fromString("uabi"); + private final MutableObjectData unitData; + private final Map unitIdToUnitType = new HashMap<>(); + private final CAbilityData abilityData; - public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, - final float y, final float facing, final BufferedImage buildingPathingPixelMap, - final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { - final MutableGameObject unitType = this.unitData.get(typeId); - final int handleId = handleIdAllocator.createId(); - final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); - final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); - final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); - final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); - final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + public CUnitData(final MutableObjectData unitData, final CAbilityData abilityData) { + this.unitData = unitData; + this.abilityData = abilityData; + } - final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); + public CUnit create(final CSimulation simulation, final int playerIndex, final War3ID typeId, final float x, + final float y, final float facing, final BufferedImage buildingPathingPixelMap, + final SimulationRenderController simulationRenderController, final HandleIdAllocator handleIdAllocator) { + final MutableGameObject unitType = this.unitData.get(typeId); + final int handleId = handleIdAllocator.createId(); + final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0); + final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); + final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); + final int defense = unitType.getFieldAsInteger(DEFENSE, 0); + final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); - final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, - speed, defense, unitTypeInstance); - if (speed > 0) { - unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); - } - if (!unitTypeInstance.getAttacks().isEmpty()) { - unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); - } - return unit; - } + final CUnitType unitTypeInstance = getUnitTypeInstance(typeId, buildingPathingPixelMap, unitType); - private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, - final MutableGameObject unitType) { - CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); - if (unitTypeInstance == null) { - final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); - final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); - final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); - final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); - final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); - final String unitName = unitType.getFieldAsString(NAME, 0); - final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); - final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); - final EnumSet targetedAs = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); - final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); - final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); - if (classificationString != null) { - final String[] classificationValues = classificationString.split(","); - for (final String unitEditorKey : classificationValues) { - final CUnitClassification unitClassification = CUnitClassification - .parseUnitClassification(unitEditorKey); - if (unitClassification != null) { - classifications.add(unitClassification); - } - } - } - final List attacks = new ArrayList<>(); - final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); - if ((attacksEnabled & 0x1) != 0) { - try { - // attack one - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } catch (Exception exc) { - System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - if ((attacksEnabled & 0x2) != 0) { - try { - // attack two - final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); - final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); - final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); - final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); - final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0); - final EnumSet areaOfEffectTargets = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); - final CAttackType attackType = CAttackType - .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); - final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); - final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); - final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); - final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); - final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); - final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); - final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); - final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); - final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); - final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); - final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); - final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); - final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, - 0); - final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); - final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); - final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); - final EnumSet targetsAllowed = CTargetType - .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); - final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); - final CWeaponType weaponType = CWeaponType - .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); - attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, - areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, - cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, - damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, - maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed, - range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType)); - } catch (Exception exc) { - System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); - } - } - final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); - final boolean raise = (deathType & 0x1) != 0; - final boolean decay = (deathType & 0x2) != 0; - final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); - final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); - final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); - final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); - unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, - attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, - targetedAs, acquisitionRange, minimumAttackRange); - this.unitIdToUnitType.put(typeId, unitTypeInstance); - } - return unitTypeInstance; - } + final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum, + speed, defense, unitTypeInstance); + if (speed > 0) { + unit.add(simulation, new CAbilityMove(handleIdAllocator.createId())); + } + if (!unitTypeInstance.getAttacks().isEmpty()) { + unit.add(simulation, new CAbilityAttack(handleIdAllocator.createId())); + } + for (final String ability : abilityList.split(",")) { + unit.add(simulation, this.abilityData.createAbility(ability, handleIdAllocator.createId())); + } + return unit; + } - private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, - final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, - final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, - final int damageBase, final float damageFactorMedium, final float damageFactorSmall, - final float damageLossFactor, final int damageDice, final int damageSidesPerDie, - final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, - final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, - final boolean projectileHomingEnabled, final int projectileSpeed, final int range, - final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, - final String weaponSound, final CWeaponType weaponType) { - final CUnitAttack attack; - switch (weaponType) { - case MISSILE: - attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed); - break; - case MBOUNCE: - attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, - areaOfEffectFullDamage, areaOfEffectTargets); - break; - case MSPLASH: - case ARTILLERY: - attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, - cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, - rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, - projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, - areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); - break; - case MLINE: - case ALINE: - attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, - projectileSpeed, damageSpillDistance, damageSpillRadius); - break; - case INSTANT: - attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType, projectileArt); - break; - default: - case NORMAL: - attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, - damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, - targetsAllowed, weaponSound, weaponType); - break; - } - return attack; - } + private CUnitType getUnitTypeInstance(final War3ID typeId, final BufferedImage buildingPathingPixelMap, + final MutableGameObject unitType) { + CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId); + if (unitTypeInstance == null) { + final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0); + final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0); + final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0); + final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0); + final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp); + final String unitName = unitType.getFieldAsString(NAME, 0); + final float acquisitionRange = unitType.getFieldAsFloat(ACQUISITION_RANGE, 0); + final float minimumAttackRange = unitType.getFieldAsFloat(MINIMUM_ATTACK_RANGE, 0); + final EnumSet targetedAs = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0)); + final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0); + final EnumSet classifications = EnumSet.noneOf(CUnitClassification.class); + if (classificationString != null) { + final String[] classificationValues = classificationString.split(","); + for (final String unitEditorKey : classificationValues) { + final CUnitClassification unitClassification = CUnitClassification + .parseUnitClassification(unitEditorKey); + if (unitClassification != null) { + classifications.add(unitClassification); + } + } + } + final List attacks = new ArrayList<>(); + final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0); + if ((attacksEnabled & 0x1) != 0) { + try { + // attack one + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 1 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + if ((attacksEnabled & 0x2) != 0) { + try { + // attack two + final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0); + final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0); + final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0); + final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0); + final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, + 0); + final EnumSet areaOfEffectTargets = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0)); + final CAttackType attackType = CAttackType + .parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0)); + final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0); + final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0); + final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0); + final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0); + final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0); + final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0); + final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0); + final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0); + final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0); + final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0); + final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0); + final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0); + final boolean projectileHomingEnabled = unitType + .getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0); + final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0); + final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0); + final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0); + final EnumSet targetsAllowed = CTargetType + .parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0)); + final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0); + final CWeaponType weaponType = CWeaponType + .parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0)); + attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage, + areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, + cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, + damageDice, damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount, + maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, + weaponType)); + } + catch (final Exception exc) { + System.err.println("Attack 2 failed to parse with: " + exc.getClass() + ":" + exc.getMessage()); + } + } + final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0); + final boolean raise = (deathType & 0x1) != 0; + final boolean decay = (deathType & 0x2) != 0; + final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0); + final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0); + final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0)); + final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0); + unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications, + attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime, + targetedAs, acquisitionRange, minimumAttackRange); + this.unitIdToUnitType.put(typeId, unitTypeInstance); + } + return unitTypeInstance; + } - public float getPropulsionWindow(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); - } + private CUnitAttack createAttack(final float animationBackswingPoint, final float animationDamagePoint, + final int areaOfEffectFullDamage, final int areaOfEffectMediumDamage, final int areaOfEffectSmallDamage, + final EnumSet areaOfEffectTargets, final CAttackType attackType, final float cooldownTime, + final int damageBase, final float damageFactorMedium, final float damageFactorSmall, + final float damageLossFactor, final int damageDice, final int damageSidesPerDie, + final float damageSpillDistance, final float damageSpillRadius, final int damageUpgradeAmount, + final int maximumNumberOfTargets, final float projectileArc, final String projectileArt, + final boolean projectileHomingEnabled, final int projectileSpeed, final int range, + final float rangeMotionBuffer, final boolean showUI, final EnumSet targetsAllowed, + final String weaponSound, final CWeaponType weaponType) { + final CUnitAttack attack; + switch (weaponType) { + case MISSILE: + attack = new CUnitAttackMissile(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed); + break; + case MBOUNCE: + attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets, + areaOfEffectFullDamage, areaOfEffectTargets); + break; + case MSPLASH: + case ARTILLERY: + attack = new CUnitAttackMissileSplash(animationBackswingPoint, animationDamagePoint, attackType, + cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, + rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, + projectileHomingEnabled, projectileSpeed, areaOfEffectFullDamage, areaOfEffectMediumDamage, + areaOfEffectSmallDamage, areaOfEffectTargets, damageFactorMedium, damageFactorSmall); + break; + case MLINE: + case ALINE: + attack = new CUnitAttackMissileLine(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt, projectileHomingEnabled, + projectileSpeed, damageSpillDistance, damageSpillRadius); + break; + case INSTANT: + attack = new CUnitAttackInstant(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType, projectileArt); + break; + default: + case NORMAL: + attack = new CUnitAttackNormal(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, + damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, + targetsAllowed, weaponSound, weaponType); + break; + } + return attack; + } - public float getTurnRate(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); - } + public float getPropulsionWindow(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROPULSION_WINDOW, 0); + } - public boolean isBuilding(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); - } + public float getTurnRate(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(TURN_RATE, 0); + } - public String getName(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getName(); - } + public boolean isBuilding(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsBoolean(IS_BLDG, 0); + } - public int getA1MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); - } + public String getName(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getName(); + } - public int getA1MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); - } + public int getA1MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0); + } - public int getA2MinDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); - } + public int getA1MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0)); + } - public int getA2MaxDamage(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) - + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) - * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); - } + public int getA2MinDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0); + } - public int getDefense(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); - } + public int getA2MaxDamage(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_BASE, 0) + + (this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_DICE, 0) + * this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0)); + } - public int getA1ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); - } + public int getDefense(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0); + } - public float getA1ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); - } + public int getA1ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0); + } - public int getA2ProjectileSpeed(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); - } + public float getA1ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0); + } - public float getA2ProjectileArc(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); - } + public int getA2ProjectileSpeed(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0); + } - public String getA1MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); - } + public float getA2ProjectileArc(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0); + } - public String getA2MissileArt(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); - } + public String getA1MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0); + } - public float getA1Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); - } + public String getA2MissileArt(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0); + } - public float getA2Cooldown(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); - } + public float getA1Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0); + } - public float getProjectileLaunchX(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); - } + public float getA2Cooldown(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0); + } - public float getProjectileLaunchY(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); - } + public float getProjectileLaunchX(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_X, 0); + } - public float getProjectileLaunchZ(final War3ID unitTypeId) { - return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); - } + public float getProjectileLaunchY(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Y, 0); + } + + public float getProjectileLaunchZ(final War3ID unitTypeId) { + return this.unitData.get(unitTypeId).getFieldAsFloat(PROJECTILE_LAUNCH_Z, 0); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java deleted file mode 100644 index a5643e0..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehavior.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; - -public interface CBehavior { - /** - * Executes one step of game simulation of the current order, returning true if - * the order has completed. Many orders may wrap the move order and spend a - * number of simulation steps moving to get within range of the target point - * before completing. - * - * @return - */ - boolean update(CSimulation game); - - /** - * Gets the Order ID of the order, useful for determining which icon to - * highlight on the unit's command card. - * - * @return - */ - int getOrderId(); -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java deleted file mode 100644 index c444bfb..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorAttack.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.util.WarsmashConstants; -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; - -public class CBehaviorAttack implements CBehavior { - private final CUnit unit; - private final int orderId; - private boolean wasWithinPropWindow = false; - private final CUnitAttack unitAttack; - private final CWidget target; - private int damagePointLaunchTime; - private int backSwingTime; - private CBehavior moveOrder; - private int thisOrderCooldownEndTime; - private boolean wasInRange = false; - - public CBehaviorAttack(final CUnit unit, final CUnitAttack unitAttack, final int orderId, final CWidget target) { - this.unit = unit; - this.unitAttack = unitAttack; - this.orderId = orderId; - this.target = target; - createMoveOrder(unit, target); - } - - private void createMoveOrder(final CUnit unit, final CWidget target) { - if (!unit.isMovementDisabled()) { // TODO: Check mobility instead - if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) { - this.moveOrder = new CBehaviorMove(unit, this.orderId, (CUnit) target); - } - else { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); - } - } - else { - this.moveOrder = null; - } - } - - @Override - public boolean update(final CSimulation simulation) { - if (this.target.isDead() - || !this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed())) { - return true; - } - float range = this.unitAttack.getRange(); - if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CBehaviorMove) - && (this.damagePointLaunchTime != 0 /* - * only apply range motion buffer if they were already in range and - * attacked - */)) { - range += this.unitAttack.getRangeMotionBuffer(); - } - if (!this.unit.canReach(this.target, range) - || (this.unit.distance(this.target) < this.unit.getUnitType().getMinimumAttackRange())) { - if (this.moveOrder == null) { - return true; - } - if (this.moveOrder.update(simulation)) { - return true; // we just cant reach them - } - this.wasInRange = false; - this.damagePointLaunchTime = 0; - this.thisOrderCooldownEndTime = 0; - return false; - } - this.wasInRange = true; - if (!this.unit.isMovementDisabled()) { - final float prevX = this.unit.getX(); - final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; - 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.getGameplayConstants().getAttackHalfAngle(); - final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - - 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) { - this.wasWithinPropWindow = true; - } - else { - // If this happens, the unit is facing the wrong way, and has to turn before - // moving. - this.wasWithinPropWindow = false; - } - } - else { - this.wasWithinPropWindow = true; - } - - final int cooldownEndTime = this.unit.getCooldownEndTime(); - final int currentTurnTick = simulation.getGameTurnTick(); - if (this.wasWithinPropWindow) { - if (this.damagePointLaunchTime != 0) { - if (currentTurnTick >= this.damagePointLaunchTime) { - int minDamage = this.unitAttack.getMinDamage(); - final int maxDamage = Math.max(0, this.unitAttack.getMaxDamage()); - if (minDamage > maxDamage) { - minDamage = maxDamage; - } - final int damage; - if (maxDamage == 0) { - damage = 0; - } - else if (minDamage == maxDamage) { - damage = minDamage; - } - else { - damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage; - } - this.unitAttack.launch(simulation, this.unit, this.target, damage); - this.damagePointLaunchTime = 0; - } - } - else if (currentTurnTick >= cooldownEndTime) { - final float cooldownTime = this.unitAttack.getCooldownTime(); - final float animationBackswingPoint = this.unitAttack.getAnimationBackswingPoint(); - final int a1CooldownSteps = (int) (cooldownTime / WarsmashConstants.SIMULATION_STEP_TIME); - final int a1BackswingSteps = (int) (animationBackswingPoint / WarsmashConstants.SIMULATION_STEP_TIME); - final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint() - / WarsmashConstants.SIMULATION_STEP_TIME); - this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps); - this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps; - this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps; - this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps; - this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f, - true); - this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false); - } - else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, - false); - } - } - else { - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f, - false); - } - - return false; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java deleted file mode 100644 index c1891d2..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorPatrol.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; -import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; - -public class CBehaviorPatrol implements CBehavior { - private final CUnit unit; - private final int orderId; - private final CWidget target; - private CBehavior moveOrder; - - public CBehaviorPatrol(final CUnit unit, final int orderId, final CUnit target) { - this.unit = unit; - this.orderId = orderId; - this.target = target; - createMoveOrder(unit, target); - } - - private void createMoveOrder(final CUnit unit, final CUnit target) { - if (!unit.isMovementDisabled()) { // TODO: Check mobility instead - if ((target instanceof CUnit) && !(target.getUnitType().isBuilding())) { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target); - } - else { - this.moveOrder = new CBehaviorMove(unit, this.orderId, target.getX(), target.getY()); - } - } - else { - this.moveOrder = null; - } - } - - @Override - public boolean update(final CSimulation simulation) { - if (this.target.isDead()) { - return true; - } - final float range = this.unit.getAcquisitionRange(); - if (!this.unit.canReach(this.target, range)) { - if (this.moveOrder == null) { - return true; - } - if (this.moveOrder.update(simulation)) { - return true; // we just cant reach them - } - return false; - } - if (!this.unit.isMovementDisabled()) { - final float prevX = this.unit.getX(); - final float prevY = this.unit.getY(); - final float deltaY = this.target.getY() - prevY; - final float deltaX = this.target.getX() - prevX; - 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 turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId()); - - 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); - } - } - else { - } - this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, false); - return false; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java deleted file mode 100644 index f850e63..0000000 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/CBehaviorStop.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; - -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; - -public class CBehaviorStop implements CBehavior { - private final int orderId; - - public CBehaviorStop(final int orderId) { - this.orderId = orderId; - } - - @Override - public boolean update(final CSimulation game) { - return true; - } - - @Override - public int getOrderId() { - return this.orderId; - } - -} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java new file mode 100644 index 0000000..2755117 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrder.java @@ -0,0 +1,18 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +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.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public interface COrder { + int getAbilityHandleId(); + + int getOrderId(); + + CBehavior begin(final CSimulation game, CUnit caster); + + final StringMsgTargetCheckReceiver targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); + final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java new file mode 100644 index 0000000..299686f --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderNoTarget.java @@ -0,0 +1,33 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; + +public class COrderNoTarget implements COrder { + private final int abilityHandleId; + private final int orderId; + + public COrderNoTarget(final int abilityHandleId, final int orderId) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + return ability.beginNoTarget(game, caster, this.orderId); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java new file mode 100644 index 0000000..462a3aa --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetPoint.java @@ -0,0 +1,57 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; + +import com.badlogic.gdx.math.Vector2; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public class COrderTargetPoint implements COrder { + private final int abilityHandleId; + private final int orderId; + private final Vector2 target; + + public COrderTargetPoint(final int abilityHandleId, final int orderId, final Vector2 target) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.target = target; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + public Vector2 getTarget() { + return this.target; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, this.abilityActivationReceiver.reset()); + if (this.abilityActivationReceiver.isUseOk()) { + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + ability.checkCanTarget(game, caster, this.orderId, this.target, targetReceiver); + if (targetReceiver.getTarget() != null) { + return ability.begin(game, caster, this.orderId, this.target); + } + else { + game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + else { + game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java index 13ce91a..8b4a1d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/orders/COrderTargetWidget.java @@ -1,5 +1,52 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders; -public class COrderTargetWidget { - +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; + +public class COrderTargetWidget implements COrder { + private final int abilityHandleId; + private final int orderId; + private final int targetHandleId; + + public COrderTargetWidget(final int abilityHandleId, final int orderId, final int targetHandleId) { + this.abilityHandleId = abilityHandleId; + this.orderId = orderId; + this.targetHandleId = targetHandleId; + } + + @Override + public int getAbilityHandleId() { + return this.abilityHandleId; + } + + @Override + public int getOrderId() { + return this.orderId; + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster) { + final CAbility ability = game.getAbility(this.abilityHandleId); + ability.checkCanUse(game, caster, this.orderId, abilityActivationReceiver.reset()); + if (abilityActivationReceiver.isUseOk()) { + final CUnit target = game.getUnit(this.targetHandleId); + final StringMsgTargetCheckReceiver targetReceiver = (StringMsgTargetCheckReceiver) targetCheckReceiver; + ability.checkCanTarget(game, caster, this.orderId, target, targetReceiver); + if (targetReceiver.getTarget() != null) { + return ability.begin(game, caster, this.orderId, targetReceiver.getTarget()); + } + else { + game.getCommandErrorListener().showCommandError(targetReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } + else { + game.getCommandErrorListener().showCommandError(this.abilityActivationReceiver.getMessage()); + return caster.pollNextOrderBehavior(game); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java index 0ba7d64..c51c6f3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderExecutor.java @@ -3,21 +3,14 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; import com.badlogic.gdx.math.Vector2; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgAbilityActivationReceiver; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.StringMsgTargetCheckReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderNoTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetPoint; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.COrderTargetWidget; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.CommandErrorListener; public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { private final CSimulation game; private final CommandErrorListener errorListener; - private final StringMsgTargetCheckReceiver targetCheckReceiver = new StringMsgTargetCheckReceiver<>(); - private final StringMsgAbilityActivationReceiver abilityActivationReceiver = new StringMsgAbilityActivationReceiver(); - - private StringMsgTargetCheckReceiver targetCheckReceiver() { - return (StringMsgTargetCheckReceiver) this.targetCheckReceiver.reset(); - } public CPlayerUnitOrderExecutor(final CSimulation game, final CommandErrorListener errorListener) { this.game = game; @@ -25,77 +18,24 @@ public class CPlayerUnitOrderExecutor implements CPlayerUnitOrderListener { } @Override - public boolean issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + public void issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final int targetHandleId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final CUnit target = this.game.getUnit(targetHandleId); - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); - if (targetReceiver.getTarget() != null) { - ability.onOrder(this.game, unit, orderId, target, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderTargetWidget(abilityHandleId, orderId, targetHandleId), queue); } @Override - public boolean issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, + public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, final float y, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final Vector2 target = new Vector2(x, y); - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTarget(this.game, unit, orderId, target, targetReceiver); - if (targetReceiver.getTarget() != null) { - ability.onOrder(this.game, unit, orderId, target, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderTargetPoint(abilityHandleId, orderId, new Vector2(x, y)), queue); } @Override - public boolean issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final boolean queue) { final CUnit unit = this.game.getUnit(unitHandleId); - final CAbility ability = this.game.getAbility(abilityHandleId); - ability.checkCanUse(this.game, unit, orderId, this.abilityActivationReceiver.reset()); - if (this.abilityActivationReceiver.isUseOk()) { - final StringMsgTargetCheckReceiver targetReceiver = this.targetCheckReceiver(); - ability.checkCanTargetNoTarget(this.game, unit, orderId, targetReceiver); - if (targetReceiver.getMessage() == null) { - ability.onOrderNoTarget(this.game, unit, orderId, queue); - return true; - } - else { - this.errorListener.showCommandError(targetReceiver.getMessage()); - return false; - } - } - else { - this.errorListener.showCommandError(this.abilityActivationReceiver.getMessage()); - return false; - } + unit.order(this.game, new COrderNoTarget(abilityHandleId, orderId), queue); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java index a0d420a..e9bf4cd 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListener.java @@ -1,12 +1,12 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; public interface CPlayerUnitOrderListener { - boolean issueTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, boolean queue); + void issueTargetOrder(int unitHandleId, int abilityHandleId, int orderId, int targetHandleId, boolean queue); - boolean issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); + void issuePointOrder(int unitHandleId, int abilityHandleId, int orderId, float x, float y, boolean queue); // Below: used for "DROP ITEM AT POINT" ???? // boolean issueTargetAndPointOrder(int unitHandleId, int orderId, int targetHandleId, float x, float y); - boolean issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); + void issueImmediateOrder(int unitHandleId, int abilityHandleId, int orderId, boolean queue); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index e0f0fb5..0af1413 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -23,6 +23,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { private CommandButton commandButton; private int abilityHandleId; private int orderId; + private int autoCastOrderId; private final CommandCardCommandListener commandCardCommandListener; public CommandCardIcon(final String name, final UIFrame parent, @@ -45,7 +46,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.iconFrame.setVisible(false); this.activeHighlightFrame.setVisible(false); this.cooldownFrame.setVisible(false); - this.autocastFrame.setVisible(false); + this.autocastFrame.setSequence(PrimaryTag.DEATH); } else { if (commandButton.isEnabled()) { @@ -64,19 +65,24 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.cooldownFrame .setFrameByRatio(1 - (commandButton.getCooldownRemaining() / commandButton.getCooldown())); } - this.autocastFrame.setVisible(commandButton.isAutoCastActive()); } } public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, - final boolean active) { + final int autoCastOrderId, final boolean active, final boolean autoCastActive) { this.iconFrame.setVisible(true); this.activeHighlightFrame.setVisible(active); this.cooldownFrame.setVisible(false); - this.autocastFrame.setVisible(false); + if (autoCastActive) { + this.autocastFrame.setSequence(PrimaryTag.STAND); + } + else { + this.autocastFrame.setSequence(-1); + } this.iconFrame.setTexture(texture); this.abilityHandleId = abilityHandleId; this.orderId = orderId; + this.autoCastOrderId = autoCastOrderId; } @Override @@ -102,7 +108,7 @@ public class CommandCardIcon extends AbstractRenderableFrame { this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.orderId); } else if (button == Input.Buttons.RIGHT) { - this.commandCardCommandListener.toggleAutoCastAbility(this.abilityHandleId); + this.commandCardCommandListener.startUsingAbility(this.abilityHandleId, this.autoCastOrderId); } return this; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 0deff81..5c812fa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -13,11 +13,10 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; -import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; -import com.badlogic.gdx.utils.viewport.Viewport; +import com.badlogic.gdx.utils.viewport.ExtendViewport; import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; @@ -77,7 +76,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private static final Vector3 clickLocationTemp = new Vector3(); private static final Vector2 clickLocationTemp2 = new Vector2(); private final DataSource dataSource; - private final Viewport uiViewport; + private final ExtendViewport uiViewport; private final FreeTypeFontGenerator fontGenerator; private final Scene uiScene; private final Scene portraitScene; @@ -137,10 +136,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma private int selectedSoundCount = 0; private final ActiveCommandUnitTargetFilter activeCommandUnitTargetFilter; - public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator, - final Scene uiScene, final Scene portraitScene, final CameraPreset[] cameraPresets, - final CameraRates cameraRates, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener, - final CPlayerUnitOrderListener unitOrderListener) { + // TODO these corrections are used for old hardcoded UI stuff, we should + // probably remove them later + private final float widthRatioCorrection; + private final float heightRatioCorrection; + + public MeleeUI(final DataSource dataSource, final ExtendViewport uiViewport, + final FreeTypeFontGenerator fontGenerator, final Scene uiScene, final Scene portraitScene, + final CameraPreset[] cameraPresets, final CameraRates cameraRates, final War3MapViewer war3MapViewer, + final RootFrameListener rootFrameListener, final CPlayerUnitOrderListener unitOrderListener) { this.dataSource = dataSource; this.uiViewport = uiViewport; this.fontGenerator = fontGenerator; @@ -161,11 +165,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq, "UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp"); this.activeCommandUnitTargetFilter = new ActiveCommandUnitTargetFilter(); + this.widthRatioCorrection = this.uiViewport.getMinWorldWidth() / 1600f; + this.heightRatioCorrection = this.uiViewport.getMinWorldHeight() / 1200f; } private MeleeUIMinimap createMinimap(final War3MapViewer war3MapViewer) { - final Rectangle minimapDisplayArea = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f); + final Rectangle minimapDisplayArea = new Rectangle(18.75f * this.widthRatioCorrection, + 13.75f * this.heightRatioCorrection, 278.75f * this.widthRatioCorrection, + 276.25f * this.heightRatioCorrection); Texture minimapTexture = null; if (war3MapViewer.dataSource.has("war3mapMap.tga")) { try { @@ -396,11 +404,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.errorMessageFrame.setText(message); } - @Override - public void toggleAutoCastAbility(final int abilityHandleId) { - - } - public void update(final float deltaTime) { this.portrait.update(); @@ -468,8 +471,6 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.cameraManager.updateCamera(); } - private final ShapeRenderer shapeRenderer = new ShapeRenderer(); - public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) { this.rootFrame.render(batch, font20, glyphLayout); if (this.selectedUnit != null) { @@ -658,10 +659,11 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, - final int abilityHandleId, final int orderId, final boolean active) { + final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, + final boolean autoCastActive) { final int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); final int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); - this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, active); + this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, autoCastActive); } public void resize(final Rectangle viewport) { @@ -670,10 +672,10 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } public void positionPortrait() { - this.projectionTemp1.x = 422; - this.projectionTemp1.y = 57; - this.projectionTemp2.x = 422 + 167; - this.projectionTemp2.y = 57 + 170; + this.projectionTemp1.x = 422 * this.widthRatioCorrection; + this.projectionTemp1.y = 57 * this.heightRatioCorrection; + this.projectionTemp2.x = (422 + 167) * this.widthRatioCorrection; + this.projectionTemp2.y = (57 + 170) * this.heightRatioCorrection; this.uiViewport.project(this.projectionTemp1); this.uiViewport.project(this.projectionTemp2); @@ -752,7 +754,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public void ordersChanged() { + public void ordersChanged(final int abilityHandleId, final int orderId) { clearCommandCard(); this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this, this.war3MapViewer.getAbilityDataUI()); @@ -798,20 +800,19 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.activeCommandUnitTargetFilter); final boolean shiftDown = isShiftDown(); if (rayPickUnit != null) { - if (this.unitOrderListener.issueTargetOrder( + this.unitOrderListener.issueTargetOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), this.activeCommand.getHandleId(), this.activeCommandOrderId, - rayPickUnit.getSimulationUnit().getHandleId(), shiftDown)) { - if (getSelectedUnit().soundset.yesAttack - .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } + rayPickUnit.getSimulationUnit().getHandleId(), shiftDown); + if (getSelectedUnit().soundset.yesAttack + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); + } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; } } else { @@ -829,21 +830,21 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma else { this.war3MapViewer.showConfirmation(clickLocationTemp, 0, 1, 0); } - if (this.unitOrderListener.issuePointOrder( + this.unitOrderListener.issuePointOrder( this.activeCommandUnit.getSimulationUnit().getHandleId(), this.activeCommand.getHandleId(), this.activeCommandOrderId, clickLocationTemp2.x, - clickLocationTemp2.y, shiftDown)) { - if (getSelectedUnit().soundset.yes.playUnitResponse( - this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { - portraitTalk(); - } - this.selectedSoundCount = 0; - if (!shiftDown) { - this.activeCommandUnit = null; - this.activeCommand = null; - this.activeCommandOrderId = -1; - } + clickLocationTemp2.y, shiftDown); + if (getSelectedUnit().soundset.yes + .playUnitResponse(this.war3MapViewer.worldScene.audioContext, getSelectedUnit())) { + portraitTalk(); } + this.selectedSoundCount = 0; + if (!shiftDown) { + this.activeCommandUnit = null; + this.activeCommand = null; + this.activeCommandOrderId = -1; + } + } } } @@ -975,4 +976,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } return false; } + + public float getHeightRatioCorrection() { + return this.heightRatioCorrection; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java index 2f0ea28..35591b1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandCardCommandListener.java @@ -2,6 +2,4 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui.command; public interface CommandCardCommandListener { void startUsingAbility(int abilityHandleId, int orderId); - - void toggleAutoCastAbility(int abilityHandleId); }