Update some movement code and fun with projectiles

This commit is contained in:
Retera 2020-03-01 11:04:44 -06:00
parent c4a24934dd
commit f776a602f9
23 changed files with 592 additions and 55 deletions

View File

@ -0,0 +1,35 @@
package com.etheller.warsmash;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
public class CodeCounter {
public static void main(final String[] args) {
final int sourceLines = countFile(new File("src/com"));
System.out.println(sourceLines);
}
public static int countFile(final File file) {
if (file.isDirectory()) {
int sum = 0;
for (final File subFile : file.listFiles()) {
sum += countFile(subFile);
}
return sum;
}
else {
try {
if (file.getName().toLowerCase().endsWith(".java")) {
return Files.readAllLines(file.toPath()).size();
}
}
catch (final IOException e) {
e.printStackTrace();
}
return 0;
}
}
}

View File

@ -0,0 +1,61 @@
package com.etheller.warsmash;
public class MathSpeedBenchmark {
private static final int NUMBER_OF_ITERATIONS = 100000000;
public static void main(final String[] args) {
// Let us solve for Ground Distance two ways.
long sumCosineTime = 0;
long sumSquareRootTime = 0;
final float[] thrallXs = new float[NUMBER_OF_ITERATIONS];
final float[] thrallYs = new float[NUMBER_OF_ITERATIONS];
final float[] murlocXs = new float[NUMBER_OF_ITERATIONS];
final float[] murlocYs = new float[NUMBER_OF_ITERATIONS];
for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) {
thrallXs[i] = getRandomFloat(-25000.0f, 25000.0f);
thrallYs[i] = getRandomFloat(-25000.0f, 25000.0f);
murlocXs[i] = getRandomFloat(-25000.0f, 25000.0f);
murlocYs[i] = getRandomFloat(-25000.0f, 25000.0f);
}
final long clockTime1 = System.currentTimeMillis();
for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) {
final float distance2 = groundDistanceSqrt(thrallXs[i], thrallYs[i], murlocXs[i], murlocYs[i]);
}
final long clockTime2 = System.currentTimeMillis();
for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) {
final float distance1 = groundDistanceCos(thrallXs[i], thrallYs[i], murlocXs[i], murlocYs[i]);
}
final long clockTime3 = System.currentTimeMillis();
// if (Math.abs(distance2 - distance1) > 0.1) {
// System.out.println(thrallX + "," + thrallY);
// System.out.println(murlocX + "," + murlocY);
// System.err.println(distance1 + " != " + distance2);
// throw new RuntimeException("You have failed to do mathematics.");
// }
sumCosineTime = clockTime2 - clockTime1;
sumSquareRootTime = clockTime3 - clockTime2;
System.out.println("Square Root: " + sumCosineTime);
System.out.println("Cosine: " + sumSquareRootTime);
}
static float getRandomFloat(final float min, final float max) {
final float range = max - min;
return (float) ((Math.random() * range) + min);
}
static float groundDistanceSqrt(final float thrallX, final float thrallY, final float murlocX,
final float murlocY) {
final float dx = murlocX - thrallX;
final float dy = murlocY - thrallY;
return (float) Math.sqrt((dx * dx) + (dy * dy));
}
static float groundDistanceCos(final float thrallX, final float thrallY, final float murlocX, final float murlocY) {
final float dx = murlocX - thrallX;
final float dy = murlocY - thrallY;
final double angle = Math.atan2(dy, dx);
return (float) (dx / Math.cos(angle));
}
}

View File

@ -24,6 +24,7 @@ import com.etheller.warsmash.viewer5.ModelViewer;
import com.etheller.warsmash.viewer5.PathSolver;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.SolvedPath;
import com.etheller.warsmash.viewer5.handlers.mdx.EventObjectEmitterObject;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
@ -74,7 +75,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.cameraManager.setupCamera(scene);
// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\MainMenu\\MainMenu3D_exp\\MainMenu3D_exp.mdx",
this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\RisingWaterDoodad\\RisingWaterDoodad.mdx",
this.mainModel = (MdxModel) this.viewer.load("Units\\Human\\HeroPaladinBoss\\HeroPaladinBoss.mdx",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
@ -82,6 +83,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
}
}, null);
final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1);
for (final Sequence seq : this.mainModel.getSequences()) {
System.out.println(seq.getName() + ": " + Arrays.toString(seq.getInterval()));
}
System.out.println(Arrays.toString(evt.keyFrames));
System.out.println(evt.name);
// this.modelCamera = this.mainModel.cameras.get(0);
this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);

View File

@ -13,7 +13,6 @@ import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.GL30;
@ -84,6 +83,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
private Rectangle minimapFilledArea;
private final Texture[] teamColors = new Texture[WarsmashConstants.MAX_PLAYERS];
private Texture solidGreenTexture;
@Override
public void create() {
@ -115,15 +115,15 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
.createDataSource();
this.viewer = new War3MapViewer(this.codebase, this);
this.viewer.worldScene.enableAudio();
this.viewer.enableAudio();
try {
// "Maps\\Campaign\\NightElf03.w3m"
this.viewer.loadMap("Maps\\Campaign\\NightElf03.w3m");
this.viewer.loadMap("ProjectileTest.w3x");
}
catch (final IOException e) {
throw new RuntimeException(e);
}
this.viewer.worldScene.enableAudio();
this.viewer.enableAudio();
this.cameraManager = new CameraManager();
this.cameraManager.setupCamera(this.viewer.worldScene);
@ -200,13 +200,16 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp");
}
Gdx.input.setInputProcessor(this);
this.solidGreenTexture = ImageUtils.getBLPTexture(this.viewer.dataSource,
"ReplaceableTextures\\TeamColor\\TeamColor06.blp");
final Music music = Gdx.audio
.newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\NightElf3.mp3"));
music.setVolume(0.2f);
music.setLooping(true);
music.play();
Gdx.input.setInputProcessor(this);
//
// final Music music = Gdx.audio
// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\OrcTheme.mp3"));
// music.setVolume(0.2f);
// music.setLooping(true);
// music.play();
this.minimap = new Rectangle(35, 7, 305, 272);
final float worldWidth = (this.viewer.terrain.columns - 1);
@ -303,6 +306,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
}
}
this.batch.draw(this.solidGreenTexture, 413, 34, 122 * (this.selectedUnit.getSimulationUnit().getLife()
/ this.selectedUnit.getSimulationUnit().getMaximumLife()), 7);
}
for (final RenderUnit unit : this.viewer.units) {
if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) {

View File

@ -3,5 +3,5 @@ package com.etheller.warsmash.util;
public class WarsmashConstants {
public static final int MAX_PLAYERS = 16;
public static final int REPLACEABLE_TEXTURE_LIMIT = 64;
public static final float SIMULATION_STEP_TIME = 1 / 60f;
public static final float SIMULATION_STEP_TIME = 1 / 20f;
}

View File

@ -48,6 +48,12 @@ public abstract class Node extends GenericNode {
return this;
}
public Node setLocation(final float x, final float y, final float z) {
this.localLocation.set(x, y, z);
this.dirty = true;
return this;
}
public Node setLocation(final float[] location) {
this.localLocation.set(location);
this.dirty = true;

View File

@ -131,6 +131,7 @@ public class Scene {
this.grid.remove(instance);
instance.scene = null;
this.instances.remove(instance);
return true;
}

View File

@ -59,7 +59,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
private int geometryEmitterType = -1;
public final String type;
private final String id;
private final long[] keyFrames;
public final long[] keyFrames;
private long globalSequence = -1;
private final long[] defval = { 1 };
public MdxModel internalModel;
@ -185,7 +185,7 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
private void load(final List<GenericResource> tables) {
final MappedData firstTable = (MappedData) tables.get(0).data;
final MappedDataRow row = firstTable.getRow(this.id);
final MappedDataRow row = firstTable.getRow(this.id.trim());
if (row != null) {
final MdxModel model = this.model;
@ -285,9 +285,12 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
}
}
else {
System.err.println("Unknown event object ID: " + this.type + this.id);
System.err.println("Unknown event object type: " + this.type + this.id);
}
}
else {
System.err.println("Unknown event object ID: " + this.type + this.id);
}
}
public int getValue(final long[] out, final MdxComplexInstance instance) {

View File

@ -340,4 +340,8 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
return this.cameras;
}
public List<EventObjectEmitterObject> getEventObjects() {
return this.eventObjects;
}
}

View File

@ -68,6 +68,19 @@ public class StandSequence {
}
}
public static void randomDeathSequence(final MdxComplexInstance target) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = selectSequence("death", sequences);
if (sequence != null) {
target.setSequence(sequence.index);
}
else {
target.setSequence(0);
}
}
public static void randomWalkSequence(final MdxComplexInstance target) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
@ -81,6 +94,19 @@ public class StandSequence {
}
}
public static void randomBirthSequence(final MdxComplexInstance target) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = selectSequence("birth", sequences);
if (sequence != null) {
target.setSequence(sequence.index);
}
else {
randomStandSequence(target);
}
}
public static void randomPortraitSequence(final MdxComplexInstance target) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();

View File

@ -10,8 +10,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import org.apache.commons.compress.utils.IOUtils;
@ -57,6 +59,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
@ -65,9 +68,11 @@ 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.projectile.CAttackProjectile;
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.ProjectileCreator;
import mpq.MPQArchive;
import mpq.MPQException;
@ -115,6 +120,7 @@ public class War3MapViewer extends ModelViewer {
public MappedData unitMetaData = new MappedData();
public List<RenderUnit> units = new ArrayList<>();
public List<RenderItem> items = new ArrayList<>();
public List<RenderAttackProjectile> projectiles = new ArrayList<>();
public boolean unitsReady;
public War3Map mapMpq;
public PathSolver mapPathSolver = PathSolver.DEFAULT;
@ -136,6 +142,8 @@ public class War3MapViewer extends ModelViewer {
private final DynamicShadowManager dynamicShadowManager = new DynamicShadowManager();
private final Random seededRandom = new Random(1337L);
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas);
this.gameDataSource = dataSource;
@ -297,7 +305,44 @@ public class War3MapViewer extends ModelViewer {
}
final Warcraft3MapObjectData modifications = this.mapMpq.readModifications();
this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities());
this.simulation = new CSimulation(modifications.getUnits(), modifications.getAbilities(),
new ProjectileCreator() {
@Override
public CAttackProjectile create(final CSimulation simulation, final CUnit source,
final int attackIndex, final CWidget target) {
final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(source.getTypeId());
final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(source.getTypeId());
String a1MissileArt = simulation.getUnitData().getA1MissileArt(source.getTypeId());
final int a1MinDamage = simulation.getUnitData().getA1MinDamage(source.getTypeId());
final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(source.getTypeId());
final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage)
+ a1MinDamage;
if (a1MissileArt.toLowerCase().endsWith(".mdl")) {
a1MissileArt = a1MissileArt.substring(0, a1MissileArt.length() - 4);
}
if (!a1MissileArt.toLowerCase().endsWith(".mdx")) {
a1MissileArt += ".mdx";
}
final float x = source.getX();
final float y = source.getY();
final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight();
final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y, height,
a1ProjectileSpeed, a1ProjectileArc, target, source, damage);
final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver,
War3MapViewer.this.solverParams);
final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance();
modelInstance.setScene(War3MapViewer.this.worldScene);
StandSequence.randomBirthSequence(modelInstance);
modelInstance.setLocation(x, y, height);
final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile(
simulationAttackProjectile, modelInstance);
War3MapViewer.this.projectiles.add(renderAttackProjectile);
return simulationAttackProjectile;
}
});
if (this.doodadsAndDestructiblesLoaded) {
this.loadDoodadsAndDestructibles(modifications);
@ -483,7 +528,7 @@ public class War3MapViewer extends ModelViewer {
final float x = unit.getLocation()[0] - shadowX;
final float y = unit.getLocation()[1] - shadowY;
this.terrain.splats.get(texture).locations
.add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 30 });
.add(new float[] { x, y, x + shadowWidth, y + shadowHeight, 3 });
unitShadowSplat = this.terrain.splats.get(texture);
}
@ -562,6 +607,13 @@ public class War3MapViewer extends ModelViewer {
for (final RenderUnit unit : this.units) {
unit.updateAnimations(this);
}
final Iterator<RenderAttackProjectile> projectileIterator = this.projectiles.iterator();
while (projectileIterator.hasNext()) {
final RenderAttackProjectile projectile = projectileIterator.next();
if (projectile.updateAnimations(this)) {
projectileIterator.remove();
}
}
for (final RenderItem item : this.items) {
final MdxComplexInstance instance = item.instance;
final MdxComplexInstance mdxComplexInstance = instance;
@ -655,7 +707,7 @@ public class War3MapViewer extends ModelViewer {
final float y = unit.location[1];
final float z = unit.row.getFieldAsFloat(UNIT_SELECT_HEIGHT, 0);
splats.get(path).locations
.add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 35 });
.add(new float[] { x - radius, y - radius, x + radius, y + radius, z + 5 });
splats.get(path).unitMapping.add(new Consumer<SplatModel.SplatMover>() {
@Override
public void accept(final SplatMover t) {

View File

@ -0,0 +1,68 @@
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile;
public class RenderAttackProjectile {
private final CAttackProjectile simulationProjectile;
private final MdxComplexInstance modelInstance;
private float x;
private float y;
private float z;
public RenderAttackProjectile(final CAttackProjectile simulationProjectile,
final MdxComplexInstance modelInstance) {
this.simulationProjectile = simulationProjectile;
this.modelInstance = modelInstance;
this.x = simulationProjectile.getX();
this.y = simulationProjectile.getY();
this.z = simulationProjectile.getZ();
}
public boolean updateAnimations(final War3MapViewer war3MapViewer) {
if (this.simulationProjectile.isDone()) {
final MdxModel model = (MdxModel) this.modelInstance.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = StandSequence.selectSequence("death", sequences);
if (this.modelInstance.sequence != sequence.index) {
this.modelInstance.setSequence(sequence.index);
}
}
else {
if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) {
StandSequence.randomStandSequence(this.modelInstance);
}
}
final float simX = this.simulationProjectile.getX();
final float simY = this.simulationProjectile.getY();
final float simZ = this.simulationProjectile.getZ();
final float simDx = simX - this.x;
final float simDy = simY - this.y;
final float simDz = simZ - this.z;
final float simD = (float) Math.sqrt((simDx * simDx) + (simDy * simDy));
final float deltaTime = Gdx.graphics.getDeltaTime();
final float speed = Math.min(simD, this.simulationProjectile.getSpeed() * deltaTime);
if (simD > 0) {
this.x = this.x + ((speed * simDx) / simD);
this.y = this.y + ((speed * simDy) / simD);
this.z = this.z + ((speed * simDz) / simD);
}
this.modelInstance.setLocation(this.x, this.y, this.z);
war3MapViewer.worldScene.grid.moved(this.modelInstance);
final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded;
if (everythingDone) {
war3MapViewer.worldScene.removeInstance(this.modelInstance);
}
return everythingDone;
}
}

View File

@ -3,13 +3,16 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.ArrayList;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.RenderMathUtils;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset;
@ -24,6 +27,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityP
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop;
public class RenderUnit {
private static final double GLOBAL_TURN_RATE = Math.toDegrees(7f);
private static final Quaternion tempQuat = new Quaternion();
private static final War3ID RED = War3ID.fromString("uclr");
private static final War3ID GREEN = War3ID.fromString("uclg");
@ -41,11 +45,14 @@ public class RenderUnit {
private final CUnit simulationUnit;
private COrder lastOrder;
private String lastOrderAnimation;
private float flyingHeight = 0;
public SplatMover shadow;
public SplatMover selectionCircle;
private final List<CommandCardIcon> commandCardIcons = new ArrayList<>();
private float x;
private float y;
private float facing;
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset,
final MdxModel portraitModel, final CUnit simulationUnit) {
@ -56,8 +63,11 @@ public class RenderUnit {
final float[] location = unit.getLocation();
System.arraycopy(location, 0, this.location, 0, 3);
instance.move(location);
final float angle = (float) Math.toRadians(simulationUnit.getFacing());
this.facing = simulationUnit.getFacing();
final float angle = (float) Math.toRadians(this.facing);
// instance.localRotation.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle);
this.x = simulationUnit.getX();
this.y = simulationUnit.getY();
instance.rotate(tempQuat.setFromAxisRad(RenderMathUtils.VEC3_UNIT_Z, angle));
instance.scale(unit.getScale());
this.playerIndex = unit.getPlayer();
@ -65,7 +75,7 @@ public class RenderUnit {
instance.setScene(map.worldScene);
if (row != null) {
heapZ[2] = this.flyingHeight = row.getFieldAsFloat(MOVE_HEIGHT, 0);
heapZ[2] = simulationUnit.getFlyHeight();
this.location[2] += heapZ[2];
instance.move(heapZ);
@ -120,20 +130,65 @@ public class RenderUnit {
}
public void updateAnimations(final War3MapViewer map) {
final float x = this.simulationUnit.getX();
final float deltaTime = Gdx.graphics.getDeltaTime();
final float simulationX = this.simulationUnit.getX();
final float simulationY = this.simulationUnit.getY();
final float simDx = simulationX - this.x;
final float simDy = simulationY - this.y;
final float distanceToSimulation = (float) Math.sqrt((simDx * simDx) + (simDy * simDy));
final int speed = this.simulationUnit.getSpeed();
final float speedDelta = speed * deltaTime;
if (distanceToSimulation > speedDelta) {
this.x += (speedDelta * simDx) / distanceToSimulation;
this.y += (speedDelta * simDy) / distanceToSimulation;
}
else {
this.x = simulationX;
this.y = simulationY;
}
final float x = this.x;
final float dx = x - this.location[0];
this.location[0] = x;
final float y = this.simulationUnit.getY();
final float y = this.y;
final float dy = y - this.location[1];
this.location[1] = y;
this.location[2] = this.flyingHeight + map.terrain.getGroundHeight(x, y);
this.location[2] = this.simulationUnit.getFlyHeight() + map.terrain.getGroundHeight(x, y);
this.instance.moveTo(this.location);
this.instance
.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.simulationUnit.getFacing()));
float simulationFacing = this.simulationUnit.getFacing();
if (simulationFacing < 0) {
simulationFacing += 360;
}
float renderFacing = this.facing;
if (renderFacing < 0) {
renderFacing += 360;
}
float facingDelta = simulationFacing - renderFacing;
if (facingDelta < -180) {
facingDelta = 360 + facingDelta;
}
if (facingDelta > 180) {
facingDelta = -360 + facingDelta;
}
final float absoluteFacingDelta = Math.abs(facingDelta);
float angleToAdd = (float) (Math.signum(facingDelta) * GLOBAL_TURN_RATE * deltaTime);
if (absoluteFacingDelta < Math.abs(angleToAdd)) {
angleToAdd = facingDelta;
}
this.facing = (((this.facing + angleToAdd) % 360) + 360) % 360;
this.instance.setLocalRotation(tempQuat.setFromAxis(RenderMathUtils.VEC3_UNIT_Z, this.facing));
map.worldScene.grid.moved(this.instance);
final MdxComplexInstance mdxComplexInstance = this.instance;
final COrder currentOrder = this.simulationUnit.getCurrentOrder();
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1) || (currentOrder != this.lastOrder)
if (this.simulationUnit.getLife() <= 0) {
final MdxModel model = (MdxModel) mdxComplexInstance.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = StandSequence.selectSequence("death", sequences);
if ((sequence != null) && (mdxComplexInstance.sequence != sequence.index)) {
mdxComplexInstance.setSequence(sequence.index);
}
}
else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)
|| (currentOrder != this.lastOrder)
|| ((currentOrder != null) && (currentOrder.getAnimationName() != null)
&& !currentOrder.getAnimationName().equals(this.lastOrderAnimation))) {
if (this.simulationUnit.getCurrentOrder() != null) {

View File

@ -6,4 +6,9 @@ public class CDestructable extends CWidget {
super(handleId, x, y, life);
}
@Override
public float getFlyHeight() {
return 0;
}
}

View File

@ -11,4 +11,9 @@ public class CItem extends CWidget {
this.itemType = itemType;
}
@Override
public float getFlyHeight() {
return 0;
}
}

View File

@ -1,23 +1,32 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator;
public class CSimulation {
private final CUnitData unitData;
private final CAbilityData abilityData;
private final List<CUnit> units;
private final List<CAttackProjectile> projectiles;
private final HandleIdAllocator handleIdAllocator;
private final ProjectileCreator projectileCreator;
private int gameTurnTick = 0;
public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData) {
public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData,
final ProjectileCreator projectileCreator) {
this.projectileCreator = projectileCreator;
this.unitData = new CUnitData(parsedUnitData);
this.abilityData = new CAbilityData(parsedAbilityData);
this.units = new ArrayList<>();
this.projectiles = new ArrayList<>();
this.handleIdAllocator = new HandleIdAllocator();
}
@ -39,9 +48,27 @@ public class CSimulation {
return unit;
}
public CAttackProjectile createProjectile(final CUnit source, final int attackIndex, final CWidget target) {
final CAttackProjectile projectile = this.projectileCreator.create(this, source, attackIndex, target);
this.projectiles.add(projectile);
return projectile;
}
public void update() {
for (final CUnit unit : this.units) {
unit.update(this);
}
final Iterator<CAttackProjectile> projectileIterator = this.projectiles.iterator();
while (projectileIterator.hasNext()) {
final CAttackProjectile projectile = projectileIterator.next();
if (projectile.update(this)) {
projectileIterator.remove();
}
}
this.gameTurnTick++;
}
public int getGameTurnTick() {
return this.gameTurnTick;
}
}

View File

@ -9,13 +9,14 @@ import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
public class CUnit extends CWidget {
private War3ID typeId;
private float facing; // degrees
private float mana;
private int maximumLife;
private int maximumMana;
private int speed;
private int cooldownEndTime = 0;
private float flyHeight;
private final List<CAbility> abilities = new ArrayList<>();
@ -23,7 +24,8 @@ public class CUnit extends CWidget {
private final Queue<COrder> orderQueue = new LinkedList<>();
public CUnit(final int handleId, 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 float facing, final float mana, final int maximumLife, final int maximumMana, final int speed,
final float defaultFlyingHeight) {
super(handleId, x, y, life);
this.typeId = typeId;
this.facing = facing;
@ -31,6 +33,7 @@ public class CUnit extends CWidget {
this.maximumLife = maximumLife;
this.maximumMana = maximumMana;
this.speed = speed;
this.flyHeight = defaultFlyingHeight;
}
public void add(final CSimulation simulation, final CAbility ability) {
@ -121,4 +124,20 @@ public class CUnit extends CWidget {
return this.abilities;
}
public void setCooldownEndTime(final int cooldownEndTime) {
this.cooldownEndTime = cooldownEndTime;
}
public int getCooldownEndTime() {
return this.cooldownEndTime;
}
public float getFlyHeight() {
return this.flyHeight;
}
public void setFlyHeight(final float flyHeight) {
this.flyHeight = flyHeight;
}
}

View File

@ -1,6 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
public class CWidget {
public abstract class CWidget {
private final int handleId;
private float x;
private float y;
@ -41,4 +41,10 @@ public class CWidget {
this.life = life;
}
public void damage(final CUnit source, final int damage) {
this.life -= damage;
}
public abstract float getFlyHeight();
}

View File

@ -23,10 +23,19 @@ public class CUnitData {
private static final War3ID ATTACK1_DMG_BASE = War3ID.fromString("ua1b");
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_PROJECTILE_SPEED = War3ID.fromString("ua1z");
private static final War3ID ATTACK1_MISSILE_ART = War3ID.fromString("ua1m");
private static final War3ID ATTACK1_PROJECTILE_ARC = War3ID.fromString("uma1");
private static final War3ID ATTACK1_COOLDOWN = War3ID.fromString("ua1c");
private static final War3ID ATTACK2_DMG_BASE = War3ID.fromString("ua2b");
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_PROJECTILE_SPEED = War3ID.fromString("ua2z");
private static final War3ID ATTACK2_MISSILE_ART = War3ID.fromString("ua2m");
private static final War3ID ATTACK2_PROJECTILE_ARC = War3ID.fromString("uma2");
private static final War3ID ATTACK2_COOLDOWN = War3ID.fromString("ua2c");
private static final War3ID DEFENSE = War3ID.fromString("udef");
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
private final MutableObjectData unitData;
public CUnitData(final MutableObjectData unitData) {
@ -40,7 +49,9 @@ public class CUnitData {
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 CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed);
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final CUnit unit = new CUnit(handleId, x, y, life, typeId, facing, manaInitial, life, manaMaximum, speed,
moveHeight);
if (speed > 0) {
unit.add(simulation, CAbilityMove.INSTANCE);
unit.add(simulation, CAbilityPatrol.INSTANCE);
@ -96,4 +107,36 @@ public class CUnitData {
public int getDefense(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(DEFENSE, 0);
}
public int getA1ProjectileSpeed(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0);
}
public float getA1ProjectileArc(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0);
}
public int getA2ProjectileSpeed(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0);
}
public float getA2ProjectileArc(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0);
}
public String getA1MissileArt(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsString(ATTACK1_MISSILE_ART, 0);
}
public String getA2MissileArt(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsString(ATTACK2_MISSILE_ART, 0);
}
public float getA1Cooldown(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK1_COOLDOWN, 0);
}
public float getA2Cooldown(final War3ID unitTypeId) {
return this.unitData.get(unitTypeId).getFieldAsFloat(ATTACK2_COOLDOWN, 0);
}
}

View File

@ -33,7 +33,6 @@ public class CAttackOrder implements COrder {
final float absDelta = Math.abs(delta);
final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId());
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId());
final int speed = this.unit.getSpeed();
if (delta < -180) {
delta = 360 + delta;
@ -53,18 +52,6 @@ public class CAttackOrder implements COrder {
this.unit.setFacing(facing + angleToAdd);
}
if (absDelta < propulsionWindow) {
final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME;
final float speedTickSq = speedTick * speedTick;
if (((deltaX * deltaX) + (deltaY * deltaY)) <= speedTickSq) {
this.unit.setX(this.target.getX());
this.unit.setY(this.target.getY());
return true;
}
else {
this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick));
this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick));
}
this.wasWithinPropWindow = true;
}
else {
@ -73,6 +60,15 @@ public class CAttackOrder implements COrder {
this.wasWithinPropWindow = false;
}
final int cooldownEndTime = this.unit.getCooldownEndTime();
final int currentTurnTick = simulation.getGameTurnTick();
if (currentTurnTick >= cooldownEndTime) {
final float a1Cooldown = simulation.getUnitData().getA1Cooldown(this.unit.getTypeId());
final int a1CooldownSteps = (int) (a1Cooldown / WarsmashConstants.SIMULATION_STEP_TIME);
this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps);
simulation.createProjectile(this.unit, 0, this.target);
}
return false;
}
@ -83,10 +79,7 @@ public class CAttackOrder implements COrder {
@Override
public String getAnimationName() {
if (!this.wasWithinPropWindow) {
return "stand";
}
return "walk";
return "attack";
}
}

View File

@ -29,9 +29,8 @@ public class CMoveOrder implements COrder {
if (goalAngle < 0) {
goalAngle += 360;
}
final float facing = this.unit.getFacing();
float facing = this.unit.getFacing();
float delta = goalAngle - facing;
final float absDelta = Math.abs(delta);
final float propulsionWindow = simulation.getUnitData().getPropulsionWindow(this.unit.getTypeId());
final float turnRate = simulation.getUnitData().getTurnRate(this.unit.getTypeId());
final int speed = this.unit.getSpeed();
@ -42,16 +41,18 @@ public class CMoveOrder implements COrder {
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) * turnRate) * WarsmashConstants.SIMULATION_STEP_TIME) * 360;
float angleToAdd = Math.signum(delta) * (float) Math.toDegrees(turnRate);
if (absDelta < Math.abs(angleToAdd)) {
angleToAdd = delta;
}
this.unit.setFacing(facing + angleToAdd);
facing += angleToAdd;
this.unit.setFacing(facing);
}
if (absDelta < propulsionWindow) {
final float speedTick = speed * WarsmashConstants.SIMULATION_STEP_TIME;
@ -63,8 +64,9 @@ public class CMoveOrder implements COrder {
return true;
}
else {
this.unit.setX(prevX + (float) (Math.cos(goalAngleRad) * speedTick));
this.unit.setY(prevY + (float) (Math.sin(goalAngleRad) * speedTick));
final double radianFacing = Math.toRadians(facing);
this.unit.setX(prevX + (float) (Math.cos(radianFacing) * speedTick));
this.unit.setY(prevY + (float) (Math.sin(radianFacing) * speedTick));
}
this.wasWithinPropWindow = true;
}

View File

@ -0,0 +1,102 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile;
import com.etheller.warsmash.util.WarsmashConstants;
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 CAttackProjectile {
private float x;
private float y;
private float z;
private final float startingHeight;
private final float speed;
private final float arc;
private final CWidget target;
private final float halfStartingDistance;
private final float arcPeakHeight;
private float totalTravelDistance;
private boolean done;
private final CUnit source;
private final int damage;
public CAttackProjectile(final float x, final float y, final float z, final float speed, final float arc,
final CWidget target, final CUnit source, final int damage) {
this.x = x;
this.y = y;
this.z = z;
this.startingHeight = z;
this.speed = speed;
this.arc = arc;
this.target = target;
final float dx = target.getX() - x;
final float dy = target.getY() - y;
final float startingDistance = (float) Math.sqrt((dx * dx) + (dy * dy));
this.halfStartingDistance = startingDistance / 2f;
this.arcPeakHeight = arc * startingDistance;
this.source = source;
this.damage = damage;
}
public boolean update(final CSimulation cSimulation) {
final float tx = this.target.getX();
final float ty = this.target.getY();
final float sx = this.x;
final float sy = this.y;
final float dtsx = tx - sx;
final float dtsy = ty - sy;
final float dtsz = this.target.getFlyHeight() - this.startingHeight;
final float c = (float) Math.sqrt((dtsx * dtsx) + (dtsy * dtsy));
final float d1x = dtsx / c;
final float d1y = dtsy / c;
final float d1z = dtsz / (this.halfStartingDistance * 2);
float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME);
if (c <= travelDistance) {
if (!this.done) {
this.target.damage(this.source, this.damage);
}
this.done = true;
travelDistance = c;
}
final float dx = d1x * travelDistance;
final float dy = d1y * travelDistance;
this.totalTravelDistance += travelDistance;
final float dz = d1z * this.totalTravelDistance;
this.x = this.x + dx;
this.y = this.y + dy;
float firstTerm = ((1 / this.halfStartingDistance) * (this.totalTravelDistance - this.halfStartingDistance));
firstTerm = firstTerm * firstTerm;
this.z = this.startingHeight + ((-firstTerm + 1) * this.arcPeakHeight) + dz;
return this.done;
}
public float getX() {
return this.x;
}
public float getY() {
return this.y;
}
public float getZ() {
return this.z;
}
public float getSpeed() {
return this.speed;
}
public CWidget getTarget() {
return this.target;
}
public boolean isDone() {
return this.done;
}
}

View File

@ -0,0 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util;
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.projectile.CAttackProjectile;
public interface ProjectileCreator {
CAttackProjectile create(CSimulation simulation, CUnit source, int attackIndex, CWidget target);
}