Update for pathing

This commit is contained in:
Retera 2020-06-28 02:43:17 -04:00
parent 1c084e9695
commit 93f91b0f4d
24 changed files with 839 additions and 106 deletions

View File

@ -1,29 +1,7 @@
package com.etheller.warsmash;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.datasources.MpqDataSourceDescriptor;
public class TestMain {
public static void main(final String[] args) {
final MpqDataSourceDescriptor desc = new MpqDataSourceDescriptor("E:\\Backups\\Warcraft\\Data\\127\\Z.mpq");
final DataSource createDataSource = desc.createDataSource();
try {
final InputStream cliffZ = createDataSource.getResourceAsStream("ReplaceableTextures\\Cliff\\Cliff0.blp");
final BufferedImage img = ImageIO.read(cliffZ);
JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
}
catch (final IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Integer.parseInt("4294967295"));
}
}

View File

@ -14,7 +14,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;
@ -135,7 +134,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.viewer.worldScene.enableAudio();
this.viewer.enableAudio();
try {
this.viewer.loadMap("American Colo EX 1.0 unpro.w3x");
this.viewer.loadMap("Pathing.w3x");
}
catch (final IOException e) {
throw new RuntimeException(e);
@ -231,11 +230,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
Gdx.input.setInputProcessor(this);
final Music music = Gdx.audio
.newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\DarkAgents.mp3"));
music.setVolume(0.2f);
music.setLooping(true);
music.play();
// final Music music = Gdx.audio.newMusic(
// new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\War2IntroMusic.mp3"));
// music.setVolume(0.2f);
// music.setLooping(true);
// music.play();
this.minimap = new Rectangle(18.75f, 13.75f, 278.75f, 276.25f);
final float worldWidth = (this.viewer.terrain.columns - 1);
@ -269,8 +268,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
final float deltaTime = Gdx.graphics.getDeltaTime();
Gdx.gl30.glBindVertexArray(WarsmashGdxGame.VAO);
this.cameraManager.target.add(this.cameraVelocity.x * deltaTime, this.cameraVelocity.y * deltaTime, 0);
this.cameraManager.target.z = this.viewer.terrain.getGroundHeight(this.cameraManager.target.x,
this.cameraManager.target.y);
this.cameraManager.target.z = Math.max(
this.viewer.terrain.getGroundHeight(this.cameraManager.target.x, this.cameraManager.target.y),
this.viewer.terrain.getWaterHeight(this.cameraManager.target.x, this.cameraManager.target.y));
this.cameraManager.updateCamera();
this.portraitCameraManager.updateCamera();
this.viewer.updateAndRender();
@ -454,10 +454,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.rotationSpeed = (float) (Math.PI / 180);
this.zoomFactor = 0.1f;
this.horizontalAngle = 0;// (float) (Math.PI / 2);
this.verticalAngle = (float) (Math.PI / 5);
this.distance = 1600;
this.verticalAngle = (float) Math.toRadians(34);
this.distance = 1650;
this.position = new Vector3();
this.target = new Vector3(0, 0, 50);
this.target = new Vector3(0, 0, 0);
this.worldUp = new Vector3(0, 0, 1);
this.vecHeap = new Vector3();
this.quatHeap = new Quaternion();
@ -469,11 +469,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
}
private void updateCamera() {
// Limit the vertical angle so it doesn't flip.
// Since the camera uses a quaternion, flips don't matter to it, but this feels
// better.
this.verticalAngle = (float) Math.min(Math.max(0.01, this.verticalAngle), Math.PI - 0.01);
this.quatHeap.idt();
this.quatHeap.setFromAxisRad(0, 0, 1, this.horizontalAngle);
this.quatHeap2.idt();
@ -504,6 +499,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.camera.perspective(this.modelCamera.fieldOfView * 0.75f, this.camera.getAspect(),
this.modelCamera.nearClippingPlane, this.modelCamera.farClippingPlane);
}
else {
this.camera.perspective(70, this.camera.getAspect(), 100, 5000);
}
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
}
@ -666,13 +664,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
@Override
public boolean scrolled(final int amount) {
this.cameraManager.verticalAngle -= amount / 10.f;
if (this.cameraManager.verticalAngle > (Math.PI / 2)) {
this.cameraManager.verticalAngle = (float) Math.PI / 2;
}
if (this.cameraManager.verticalAngle < (Math.PI / 5)) {
this.cameraManager.verticalAngle = (float) (Math.PI / 5);
}
// this.cameraManager.verticalAngle -= amount / 10.f;
// if (this.cameraManager.verticalAngle > (Math.PI / 2)) {
// this.cameraManager.verticalAngle = (float) Math.PI / 2;
// }
// if (this.cameraManager.verticalAngle < (Math.PI / 5)) {
// this.cameraManager.verticalAngle = (float) (Math.PI / 5);
// }
return true;
}

View File

@ -1,11 +0,0 @@
package com.etheller.warsmash.gameui;
import com.etheller.warsmash.parsers.fdf.datamodel.FrameTemplateEnvironment;
public class FDFGameUI {
private final FrameTemplateEnvironment frameTemplateEnvironment;
public FDFGameUI(final FrameTemplateEnvironment frameTemplateEnvironment) {
this.frameTemplateEnvironment = frameTemplateEnvironment;
}
}

View File

@ -114,18 +114,18 @@ public class Sequence implements MdlxBlock {
private void populateTags() {
this.primaryTags.clear();
this.secondaryTags.clear();
for (final String token : this.name.split("\\s+")) {
TokenLoop: for (final String token : this.name.split("\\s+")) {
final String upperCaseToken = token.toUpperCase();
for (final PrimaryTag primaryTag : PrimaryTag.values()) {
if (upperCaseToken.equals(primaryTag.name())) {
this.primaryTags.add(primaryTag);
continue;
continue TokenLoop;
}
}
for (final SecondaryTag secondaryTag : SecondaryTag.values()) {
if (upperCaseToken.equals(secondaryTag.name())) {
this.secondaryTags.add(secondaryTag);
continue;
continue TokenLoop;
}
}
break;
@ -159,4 +159,12 @@ public class Sequence implements MdlxBlock {
public Extent getExtent() {
return this.extent;
}
public EnumSet<AnimationTokens.PrimaryTag> getPrimaryTags() {
return this.primaryTags;
}
public EnumSet<AnimationTokens.SecondaryTag> getSecondaryTags() {
return this.secondaryTags;
}
}

View File

@ -18,6 +18,7 @@ import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData;
import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.google.common.io.LittleEndianDataInputStream;
import mpq.MPQArchive;
@ -71,6 +72,15 @@ public class War3Map implements DataSource {
return environment;
}
public War3MapWpm readPathing() throws IOException {
War3MapWpm pathingMap;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(
this.dataSource.getResourceAsStream("war3map.wpm"))) {
pathingMap = new War3MapWpm(stream);
}
return pathingMap;
}
public War3MapDoo readDoodads() throws IOException {
War3MapDoo doodadsFile;
try (LittleEndianDataInputStream stream = new LittleEndianDataInputStream(

View File

@ -58,7 +58,7 @@ public class MdxModel extends com.etheller.warsmash.viewer5.Model<MdxHandler> {
@Override
public ModelInstance createInstance(final int type) {
if ((type == 1) && false) {
if (type == 1) {
return new MdxSimpleInstance(this);
}
else {

View File

@ -19,7 +19,7 @@ public class Doodad {
final boolean isSimple = row.readSLKTagBoolean("lightweight");
ModelInstance instance;
if (isSimple) {
if (isSimple && false) {
instance = model.addInstance(1);
}
else {

View File

@ -1,11 +1,14 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
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.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
public class StandSequence {
@ -26,6 +29,34 @@ public class StandSequence {
return filtered;
}
private static List<IndexedSequence> filterSequences(final PrimaryTag type, final EnumSet<SecondaryTag> tags,
final List<Sequence> sequences) {
final List<IndexedSequence> filtered = new ArrayList<>();
for (int i = 0, l = sequences.size(); i < l; i++) {
final Sequence sequence = sequences.get(i);
if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags)
&& tags.containsAll(sequence.getSecondaryTags())) {
for (final AnimationTokens.SecondaryTag secondaryTag : sequence.getSecondaryTags()) {
if (tags.contains(secondaryTag)) {
filtered.add(new IndexedSequence(sequence, i));
}
}
}
}
if (filtered.isEmpty()) {
for (int i = 0, l = sequences.size(); i < l; i++) {
final Sequence sequence = sequences.get(i);
if (sequence.getPrimaryTags().contains(type) && sequence.getSecondaryTags().containsAll(tags)
&& tags.containsAll(sequence.getSecondaryTags())) {
filtered.add(new IndexedSequence(sequence, i));
}
}
}
return filtered;
}
public static IndexedSequence selectSequence(final String type, final List<Sequence> sequences) {
final List<IndexedSequence> filtered = filterSequences(type, sequences);
@ -55,6 +86,36 @@ public class StandSequence {
return sequence;
}
public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type,
final EnumSet<AnimationTokens.SecondaryTag> tags, final List<Sequence> sequences) {
final List<IndexedSequence> filtered = filterSequences(type, tags, sequences);
filtered.sort(STAND_SEQUENCE_COMPARATOR);
int i = 0;
for (final int l = filtered.size(); i < l; i++) {
final Sequence sequence = filtered.get(i).sequence;
final float rarity = sequence.getRarity();
if (rarity == 0) {
break;
}
if ((Math.random() * 10) > rarity) {
return filtered.get(i);
}
}
final int sequencesLeft = filtered.size() - i;
final int random = (int) (i + Math.floor(Math.random() * sequencesLeft));
if (sequencesLeft <= 0) {
return null; // new IndexedSequence(null, 0);
}
final IndexedSequence sequence = filtered.get(random);
return sequence;
}
public static void randomStandSequence(final MdxComplexInstance target) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
@ -145,4 +206,18 @@ public class StandSequence {
randomStandSequence(target);
}
}
public static void randomSequence(final MdxComplexInstance target, final PrimaryTag animationName,
final EnumSet<SecondaryTag> secondaryAnimationTags) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences);
if (sequence != null) {
target.setSequence(sequence.index);
}
else {
randomStandSequence(target);
}
}
}

View File

@ -17,6 +17,7 @@ public class W3xShaders {
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" varying float a_positionHeight;\r\n" + //
" const float normalDist = 0.25;\r\n" + //
" void main() {\r\n" + //
" vec2 halfPixel = u_pixel * 0.5;\r\n" + //
@ -34,6 +35,7 @@ public class W3xShaders {
" v_uv = a_uv;\r\n" + //
" v_suv = base / u_size;\r\n" + //
" gl_Position = u_mvp * vec4(a_position.xy, height * 128.0 + a_position.z, 1.0);\r\n" + //
" a_positionHeight = a_position.z;\r\n" + //
" }\r\n" + //
" ";
@ -44,6 +46,7 @@ public class W3xShaders {
" varying vec2 v_uv;\r\n" + //
" varying vec2 v_suv;\r\n" + //
" varying vec3 v_normal;\r\n" + //
" varying float a_positionHeight;\r\n" + //
" const vec3 lightDirection = normalize(vec3(-0.3, -0.3, 0.25));\r\n" + //
" void main() {\r\n" + //
" if (any(bvec4(lessThan(v_uv, vec2(0.0)), greaterThan(v_uv, vec2(1.0))))) {\r\n" + //
@ -52,7 +55,9 @@ public class W3xShaders {
" vec4 color = texture2D(u_texture, clamp(v_uv, 0.0, 1.0)).rgba * u_color;\r\n" + //
" float shadow = texture2D(u_shadowMap, v_suv).r;\r\n" + //
" color.xyz *= clamp(dot(v_normal, lightDirection) + 0.45, 0.0, 1.0);\r\n" + //
" if (a_positionHeight <= 4.0) {;\r\n" + //
" color.xyz *= 1.0 - shadow;\r\n" + //
" };\r\n" + //
" gl_FragColor = color;\r\n" + //
" }\r\n" + //
" ";

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@ -35,6 +36,7 @@ import com.etheller.warsmash.parsers.w3x.objectdata.Warcraft3MapObjectData;
import com.etheller.warsmash.parsers.w3x.unitsdoo.War3MapUnitsDoo;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.manager.MutableObjectData;
@ -57,6 +59,7 @@ import com.etheller.warsmash.viewer5.gl.WebGL;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
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;
@ -91,6 +94,8 @@ public class War3MapViewer extends ModelViewer {
private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz");
private static final War3ID UNIT_SOUNDSET = War3ID.fromString("usnd");
private static final War3ID ITEM_FILE = War3ID.fromString("ifil");
private static final War3ID UNIT_PATHING = War3ID.fromString("upat");
private static final War3ID DESTRUCTABLE_PATHING = War3ID.fromString("bptx");
private static final War3ID sloc = War3ID.fromString("sloc");
private static final LoadGenericCallback stringDataCallback = new StringDataCallbackImplementation();
private static final float[] rayHeap = new float[6];
@ -146,6 +151,8 @@ public class War3MapViewer extends ModelViewer {
private final Random seededRandom = new Random(1337L);
private final Map<String, BufferedImage> filePathToPathingMap = new HashMap<>();
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas);
this.gameDataSource = dataSource;
@ -279,7 +286,10 @@ public class War3MapViewer extends ModelViewer {
final War3MapW3e terrainData = this.mapMpq.readEnvironment();
this.terrain = new Terrain(terrainData, w3iFile, this.webGL, this.dataSource, worldEditStrings, this);
final War3MapWpm terrainPathing = this.mapMpq.readPathing();
this.terrain = new Terrain(terrainData, terrainPathing, w3iFile, this.webGL, this.dataSource, worldEditStrings,
this);
final float[] centerOffset = terrainData.getCenterOffset();
final int[] mapSize = terrainData.getMapSize();
@ -357,7 +367,7 @@ public class War3MapViewer extends ModelViewer {
return simulationAttackProjectile;
}
});
}, this.terrain.pathingGrid);
if (this.doodadsAndDestructiblesLoaded) {
this.loadDoodadsAndDestructibles(modifications);
@ -382,8 +392,6 @@ public class War3MapViewer extends ModelViewer {
}
private void loadDoodadsAndDestructibles(final Warcraft3MapObjectData modifications) throws IOException {
final War3MapDoo dooFile = this.mapMpq.readDoodads();
this.applyModificationFile(this.doodadsData, this.doodadMetaData, modifications.getDoodads(),
WorldEditorDataType.DOODADS);
this.applyModificationFile(this.doodadsData, this.destructableMetaData, modifications.getDestructibles(),
@ -422,6 +430,18 @@ public class War3MapViewer extends ModelViewer {
this.terrain.addShadow(shadowString, doodad.getLocation()[0], doodad.getLocation()[1]);
}
final String pathingTexture = row.readSLKTag("pathTex");
if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) {
BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
if (bufferedImage == null) {
bufferedImage = TgaFile.readTGA(pathingTexture,
this.mapMpq.getResourceAsStream(pathingTexture));
this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage);
}
this.terrain.pathingGrid.blitPathingOverlayTexture(doodad.getLocation()[0],
doodad.getLocation()[1], (int) Math.toDegrees(doodad.getAngle()), bufferedImage);
}
}
// First see if the model is local.
// Doodads referring to local models may have invalid variations, so if the
@ -580,6 +600,17 @@ public class War3MapViewer extends ModelViewer {
if ((buildingShadow != null) && !"_".equals(buildingShadow)) {
this.terrain.addShadow(buildingShadow, unit.getLocation()[0], unit.getLocation()[1]);
}
final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0);
if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) {
BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
if (bufferedImage == null) {
bufferedImage = TgaFile.readTGA(pathingTexture,
this.mapMpq.getResourceAsStream(pathingTexture));
this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage);
}
this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0], unit.getLocation()[1],
(int) Math.toDegrees(unit.getAngle()), bufferedImage);
}
final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0);
UnitSoundset unitSoundset = soundsetNameToSoundset.get(soundName);
@ -816,7 +847,8 @@ public class War3MapViewer extends ModelViewer {
RenderUnit entity = null;
for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance;
if (instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) {
if (instance.isVisible(this.worldScene.camera)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) {
entity = unit;
}
}

View File

@ -0,0 +1,221 @@
package com.etheller.warsmash.viewer5.handlers.w3x.environment;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
public class PathingGrid {
private static final Map<String, MovementType> movetpToMovementType = new HashMap<>();
static {
for (final MovementType movementType : MovementType.values()) {
if (movementType != MovementType.DISABLED) {
movetpToMovementType.put(movementType.typeKey, movementType);
}
}
}
private final short[] pathingGrid;
private final short[] dynamicPathingOverlay; // for buildings and trees
private final int[] pathingGridSizes;
private final float[] centerOffset;
public PathingGrid(final War3MapWpm terrainPathing, final float[] centerOffset) {
this.centerOffset = centerOffset;
this.pathingGrid = terrainPathing.getPathing();
this.pathingGridSizes = terrainPathing.getSize();
this.dynamicPathingOverlay = new short[this.pathingGrid.length];
}
// this blit function is basically copied from HiveWE, maybe remember to mention
// that in credits as well:
// https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp
public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotation,
final BufferedImage pathingTextureTga) {
final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth();
final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight();
for (int j = 0; j < pathingTextureTga.getHeight(); j++) {
for (int i = 0; i < pathingTextureTga.getWidth(); i++) {
int x = i;
int y = j;
switch (rotation) {
case 90:
x = pathingTextureTga.getHeight() - 1 - j;
y = i;
break;
case 180:
x = pathingTextureTga.getWidth() - 1 - i;
y = pathingTextureTga.getHeight() - 1 - j;
break;
case 270:
x = j;
y = pathingTextureTga.getWidth() - 1 - i;
break;
}
// Width and height for centering change if rotation is not divisible by 180
final int xx = (getCellX(positionX) + x) - (divW / 2);
final int yy = (getCellY(positionY) + y) - (divH / 2);
if ((xx < 0) || (xx > (this.pathingGridSizes[0] - 1)) || (yy < 0)
|| (yy > (this.pathingGridSizes[1] - 1))) {
continue;
}
final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j);
byte data = 0;
if ((rgb & 0xFF) > 250) {
data |= PathingFlags.UNBUILDABLE;
}
if (((rgb & 0xFF00) >> 8) > 250) {
data |= PathingFlags.UNFLYABLE;
}
if (((rgb & 0xFF0000) >> 16) > 250) {
data |= PathingFlags.UNWALKABLE;
}
this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data;
}
}
}
public int getWidth() {
return this.pathingGridSizes[0];
}
public int getHeight() {
return this.pathingGridSizes[1];
}
public short getPathing(final float x, final float y) {
return getCellPathing(getCellX(x), getCellY(y));
}
public int getCellX(final float x) {
final float userCellSpaceX = (x - this.centerOffset[0]) / 32.0f;
final int cellX = (int) userCellSpaceX;
return cellX;
}
public int getCellY(final float y) {
final float userCellSpaceY = (y - this.centerOffset[1]) / 32.0f;
final int cellY = (int) userCellSpaceY;
return cellY;
}
public float getWorldX(final int cellX) {
return (cellX * 32f) + this.centerOffset[0] + 16f;
}
public float getWorldY(final int cellY) {
return (cellY * 32f) + this.centerOffset[1] + 16f;
}
public short getCellPathing(final int cellX, final int cellY) {
return (short) (this.pathingGrid[(cellY * this.pathingGridSizes[0]) + cellX]
| this.dynamicPathingOverlay[(cellY * this.pathingGridSizes[0]) + cellX]);
}
public boolean isPathable(final float x, final float y, final PathingType pathingType) {
return !PathingFlags.isPathingFlag(getPathing(x, y), pathingType.preventionFlag);
}
public boolean isPathable(final float x, final float y, final MovementType pathingType) {
return pathingType.isPathable(getPathing(x, y));
}
public boolean isCellPathable(final int x, final int y, final MovementType pathingType) {
return pathingType.isPathable(getCellPathing(x, y));
}
public static boolean isPathingFlag(final short pathingValue, final PathingType pathingType) {
return !PathingFlags.isPathingFlag(pathingValue, pathingType.preventionFlag);
}
// movetp referring to the unit data field of the same name
public static MovementType getMovementType(final String movetp) {
return movetpToMovementType.get(movetp);
}
public static final class PathingFlags {
public static int UNWALKABLE = 0x2;
public static int UNFLYABLE = 0x4;
public static int UNBUILDABLE = 0x8;
public static int UNSWIMABLE = 0x40; // PROBABLY, didn't confirm this flag value is accurate
public static boolean isPathingFlag(final short pathingValue, final int flag) {
return (pathingValue & flag) != 0;
}
private PathingFlags() {
}
}
public static enum MovementType {
FOOT("foot") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE);
}
},
HORSE("horse") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE);
}
},
FLY("fly") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNFLYABLE);
}
},
HOVER("hover") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE);
}
},
FLOAT("float") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE);
}
},
AMPHIBIOUS("amph") {
@Override
public boolean isPathable(final short pathingValue) {
return !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNWALKABLE)
|| !PathingFlags.isPathingFlag(pathingValue, PathingFlags.UNSWIMABLE);
}
},
DISABLED("") {
@Override
public boolean isPathable(final short pathingValue) {
return true;
}
};
private final String typeKey;
// TODO windwalk pathing type can walk through units but not through items
private MovementType(final String typeKey) {
this.typeKey = typeKey;
}
public abstract boolean isPathable(short pathingValue);
}
public static enum PathingType {
WALKABLE(PathingFlags.UNWALKABLE),
FLYABLE(PathingFlags.UNFLYABLE),
BUILDABLE(PathingFlags.UNBUILDABLE),
SWIMMABLE(PathingFlags.UNSWIMABLE);
private final int preventionFlag;
private PathingType(final int preventionFlag) {
this.preventionFlag = preventionFlag;
}
}
}

View File

@ -31,6 +31,7 @@ import com.etheller.warsmash.datasources.DataSource;
import com.etheller.warsmash.parsers.w3x.w3e.Corner;
import com.etheller.warsmash.parsers.w3x.w3e.War3MapW3e;
import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i;
import com.etheller.warsmash.parsers.w3x.wpm.War3MapWpm;
import com.etheller.warsmash.units.DataTable;
import com.etheller.warsmash.units.Element;
import com.etheller.warsmash.units.StandardObjectData;
@ -124,8 +125,9 @@ public class Terrain {
private final int testArrayBuffer;
private final int testElementBuffer;
public Terrain(final War3MapW3e w3eFile, final War3MapW3i w3iFile, final WebGL webGL, final DataSource dataSource,
final WorldEditStrings worldEditStrings, final War3MapViewer viewer) throws IOException {
public Terrain(final War3MapW3e w3eFile, final War3MapWpm terrainPathing, final War3MapW3i w3iFile,
final WebGL webGL, final DataSource dataSource, final WorldEditStrings worldEditStrings,
final War3MapViewer viewer) throws IOException {
this.webGL = webGL;
this.viewer = viewer;
this.camera = viewer.worldScene.camera;
@ -394,6 +396,7 @@ public class Terrain {
this.waveBuilder = new WaveBuilder(this.mapSize, this.waterTable, viewer, this.corners, this.centerOffset,
this.waterHeightOffset, w3eFile, w3iFile);
this.pathingGrid = new PathingGrid(terrainPathing, this.centerOffset);
}
public void createWaves() {
@ -1147,6 +1150,7 @@ public class Terrain {
static Vector3 tmp2 = new Vector3();
static Vector3 tmp3 = new Vector3();
private final WaveBuilder waveBuilder;
public PathingGrid pathingGrid;
/**
* Intersects the given ray with list of triangles. Returns the nearest
@ -1246,6 +1250,34 @@ public class Terrain {
return 0;
}
public float getWaterHeight(final float x, final float y) {
final float userCellSpaceX = (x - this.centerOffset[0]) / 128.0f;
final float userCellSpaceY = (y - this.centerOffset[1]) / 128.0f;
final int cellX = (int) userCellSpaceX;
final int cellY = (int) userCellSpaceY;
if ((cellX >= 0) && (cellX < (this.mapSize[0] - 1)) && (cellY >= 0) && (cellY < (this.mapSize[1] - 1))) {
final float bottomLeft = this.waterHeights[(cellY * this.columns) + cellX];
final float bottomRight = this.waterHeights[(cellY * this.columns) + cellX + 1];
final float topLeft = this.waterHeights[((cellY + 1) * this.columns) + cellX];
final float topRight = this.waterHeights[((cellY + 1) * this.columns) + cellX + 1];
final float sqX = userCellSpaceX - cellX;
final float sqY = userCellSpaceY - cellY;
float height;
if ((sqX + sqY) < 1) {
height = bottomLeft + ((bottomRight - bottomLeft) * sqX) + ((topLeft - bottomLeft) * sqY);
}
else {
height = topRight + ((bottomRight - topRight) * (1 - sqY)) + ((topLeft - topRight) * (1 - sqX));
}
return ((height + this.waterHeightOffset) * 128.0f);
}
return this.waterHeightOffset * 128.0f;
}
public static final class Splat {
public List<float[]> locations = new ArrayList<>();
public List<Consumer<SplatMover>> unitMapping = new ArrayList<>();

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import com.badlogic.gdx.Gdx;
@ -12,11 +13,15 @@ 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.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
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;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
@ -44,7 +49,7 @@ public class RenderUnit {
public int playerIndex;
private final CUnit simulationUnit;
private COrder lastOrder;
private String lastOrderAnimation;
private AnimationTokens.PrimaryTag lastOrderAnimation;
public SplatMover shadow;
public SplatMover selectionCircle;
private final List<CommandCardIcon> commandCardIcons = new ArrayList<>();
@ -53,6 +58,11 @@ public class RenderUnit {
private float y;
private float facing;
private boolean swimming;
private final EnumSet<AnimationTokens.SecondaryTag> secondaryAnimationTags = EnumSet
.noneOf(AnimationTokens.SecondaryTag.class);
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) {
@ -152,7 +162,30 @@ public class RenderUnit {
final float y = this.y;
final float dy = y - this.location[1];
this.location[1] = y;
this.location[2] = this.simulationUnit.getFlyHeight() + map.terrain.getGroundHeight(x, y);
final float groundHeight;
final MovementType movementType = this.simulationUnit.getUnitType().getMovementType();
final short terrainPathing = map.terrain.pathingGrid.getPathing(x, y);
final boolean swimming = (movementType == MovementType.AMPHIBIOUS)
&& PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.SWIMMABLE)
&& !PathingGrid.isPathingFlag(terrainPathing, PathingGrid.PathingType.WALKABLE);
if ((swimming) || (movementType == MovementType.FLOAT) || (movementType == MovementType.FLY)
|| (movementType == MovementType.HOVER)) {
groundHeight = Math.max(map.terrain.getGroundHeight(x, y), map.terrain.getWaterHeight(x, y));
}
else {
groundHeight = map.terrain.getGroundHeight(x, y);
}
boolean changedAnimationTags = false;
if (swimming && !this.swimming) {
this.secondaryAnimationTags.add(AnimationTokens.SecondaryTag.SWIM);
changedAnimationTags = true;
}
else if (!swimming && this.swimming) {
this.secondaryAnimationTags.remove(AnimationTokens.SecondaryTag.SWIM);
changedAnimationTags = true;
}
this.swimming = swimming;
this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight;
this.instance.moveTo(this.location);
float simulationFacing = this.simulationUnit.getFacing();
if (simulationFacing < 0) {
@ -190,14 +223,16 @@ public class RenderUnit {
else if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)
|| (currentOrder != this.lastOrder)
|| ((currentOrder != null) && (currentOrder.getAnimationName() != null)
&& !currentOrder.getAnimationName().equals(this.lastOrderAnimation))) {
&& !currentOrder.getAnimationName().equals(this.lastOrderAnimation))
|| changedAnimationTags) {
if (this.simulationUnit.getCurrentOrder() != null) {
final String animationName = this.simulationUnit.getCurrentOrder().getAnimationName();
StandSequence.randomSequence(mdxComplexInstance, animationName);
final AnimationTokens.PrimaryTag animationName = this.simulationUnit.getCurrentOrder()
.getAnimationName();
StandSequence.randomSequence(mdxComplexInstance, animationName, this.secondaryAnimationTags);
this.lastOrderAnimation = animationName;
}
else {
StandSequence.randomStandSequence(mdxComplexInstance);
StandSequence.randomSequence(mdxComplexInstance, PrimaryTag.STAND, this.secondaryAnimationTags);
}
}
this.lastOrder = currentOrder;

View File

@ -1,5 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
public interface COrder {
/**
* Executes one step of game simulation of the current order, returning true if
@ -25,5 +27,5 @@ public interface COrder {
*
* @return
*/
String getAnimationName();
AnimationTokens.PrimaryTag getAnimationName();
}

View File

@ -1,13 +1,16 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.awt.Point;
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.environment.PathingGrid;
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.pathing.CPathfindingProcessor;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.projectile.CAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator;
@ -19,10 +22,14 @@ public class CSimulation {
private final HandleIdAllocator handleIdAllocator;
private final ProjectileCreator projectileCreator;
private int gameTurnTick = 0;
private final PathingGrid pathingGrid;
private final CPathfindingProcessor pathfindingProcessor;
public CSimulation(final MutableObjectData parsedUnitData, final MutableObjectData parsedAbilityData,
final ProjectileCreator projectileCreator) {
final ProjectileCreator projectileCreator, final PathingGrid pathingGrid) {
this.projectileCreator = projectileCreator;
this.pathingGrid = pathingGrid;
this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid);
this.unitData = new CUnitData(parsedUnitData);
this.abilityData = new CAbilityData(parsedAbilityData);
this.units = new ArrayList<>();
@ -56,6 +63,15 @@ public class CSimulation {
return projectile;
}
public PathingGrid getPathingGrid() {
return this.pathingGrid;
}
public List<Point> findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY,
final PathingGrid.MovementType movementType) {
return this.pathfindingProcessor.findNaiveSlowPath(startX, startY, goalX, goalY, movementType);
}
public void update() {
for (final CUnit unit : this.units) {
unit.update(this);

View File

@ -0,0 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
/**
* Provides limited access to game map data when necessary for the game
* simulation logic. Do not add methods to this to query anything that isn't
* going to be network sync'ed.
*/
public interface CSimulationMapData {
short getTerrainPathing(float x, float y);
}

View File

@ -23,10 +23,11 @@ public class CUnit extends CWidget {
private COrder currentOrder;
private final Queue<COrder> orderQueue = new LinkedList<>();
private final CUnitType unitType;
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 float defaultFlyingHeight) {
final int speed, final CUnitType unitType) {
super(handleId, x, y, life);
this.typeId = typeId;
this.facing = facing;
@ -34,7 +35,8 @@ public class CUnit extends CWidget {
this.maximumLife = maximumLife;
this.maximumMana = maximumMana;
this.speed = speed;
this.flyHeight = defaultFlyingHeight;
this.flyHeight = unitType.getDefaultFlyingHeight();
this.unitType = unitType;
}
public void add(final CSimulation simulation, final CAbility ability) {
@ -150,4 +152,8 @@ public class CUnit extends CWidget {
this.playerIndex = playerIndex;
}
public CUnitType getUnitType() {
return this.unitType;
}
}

View File

@ -0,0 +1,26 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
/**
* The quick (symbol table instead of map) lookup for unit type values that we
* probably cannot change per unit instance.
*/
public class CUnitType {
private final PathingGrid.MovementType movementType;
private final float defaultFlyingHeight;
public CUnitType(final MovementType movementType, final float defaultFlyingHeight) {
this.movementType = movementType;
this.defaultFlyingHeight = defaultFlyingHeight;
}
public float getDefaultFlyingHeight() {
return this.defaultFlyingHeight;
}
public PathingGrid.MovementType getMovementType() {
return this.movementType;
}
}

View File

@ -1,10 +1,15 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data;
import java.util.HashMap;
import java.util.Map;
import com.etheller.warsmash.units.manager.MutableObjectData;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.War3ID;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityHoldPosition;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove;
@ -39,7 +44,9 @@ public class CUnitData {
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 static final War3ID MOVE_TYPE = War3ID.fromString("umvt");
private final MutableObjectData unitData;
private final Map<War3ID, CUnitType> unitIdToUnitType = new HashMap<>();
public CUnitData(final MutableObjectData unitData) {
this.unitData = unitData;
@ -53,8 +60,10 @@ public class CUnitData {
final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0);
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
speed, moveHeight);
speed, new CUnitType(movementType, moveHeight));
if (speed > 0) {
unit.add(simulation, CAbilityMove.INSTANCE);
unit.add(simulation, CAbilityPatrol.INSTANCE);

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
@ -80,8 +81,8 @@ public class CAttackOrder implements COrder {
}
@Override
public String getAnimationName() {
return "attack";
public AnimationTokens.PrimaryTag getAnimationName() {
return AnimationTokens.PrimaryTag.ATTACK;
}
}

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
@ -21,8 +22,8 @@ public class CDoNothingOrder implements COrder {
}
@Override
public String getAnimationName() {
return "stand";
public AnimationTokens.PrimaryTag getAnimationName() {
return AnimationTokens.PrimaryTag.STAND;
}
}

View File

@ -1,6 +1,12 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
import java.awt.Point;
import java.util.List;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
@ -11,6 +17,8 @@ public class CMoveOrder implements COrder {
private final float targetX;
private final float targetY;
private boolean wasWithinPropWindow = false;
private List<Point> path = null;
private boolean recalculated = false;
public CMoveOrder(final CUnit unit, final float targetX, final float targetY) {
this.unit = unit;
@ -22,8 +30,82 @@ public class CMoveOrder implements COrder {
public boolean update(final CSimulation simulation) {
final float prevX = this.unit.getX();
final float prevY = this.unit.getY();
final float deltaY = this.targetY - prevY;
final float deltaX = this.targetX - prevX;
final MovementType movementType = this.unit.getUnitType().getMovementType();
final PathingGrid pathingGrid = simulation.getPathingGrid();
final int startCellX = pathingGrid.getCellX(prevX);
final int startCellY = pathingGrid.getCellY(prevY);
final int goalCellX = pathingGrid.getCellX(this.targetX);
final int goalCellY = pathingGrid.getCellY(this.targetY);
if (this.path == null) {
this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY, movementType);
// check for smoothing
if (!this.path.isEmpty()) {
int lastX = startCellX;
int lastY = startCellY;
int smoothingGroupStartX = startCellX;
int smoothingGroupStartY = startCellY;
final Point firstPathElement = this.path.get(0);
double totalPathDistance = firstPathElement.distance(lastX, lastY);
lastX = firstPathElement.x;
lastY = firstPathElement.y;
int smoothingStartIndex = -1;
for (int i = 0; i < (this.path.size() - 1); i++) {
final Point nextPossiblePathElement = this.path.get(i + 1);
totalPathDistance += nextPossiblePathElement.distance(lastX, lastY);
if ((totalPathDistance < (1.25
* nextPossiblePathElement.distance(smoothingGroupStartX, smoothingGroupStartY)))
&& pathingGrid.isCellPathable((smoothingGroupStartX + nextPossiblePathElement.x) / 2,
(smoothingGroupStartY + nextPossiblePathElement.y) / 2, movementType)) {
if (smoothingStartIndex == -1) {
smoothingStartIndex = i;
}
}
else {
if (smoothingStartIndex != -1) {
for (int j = smoothingStartIndex; j < i; j++) {
final Point removed = this.path.remove(j);
}
i = smoothingStartIndex;
}
smoothingStartIndex = -1;
final Point smoothGroupNext = this.path.get(i);
smoothingGroupStartX = smoothGroupNext.x;
smoothingGroupStartY = smoothGroupNext.y;
totalPathDistance = nextPossiblePathElement.distance(smoothGroupNext);
}
lastX = nextPossiblePathElement.x;
lastY = nextPossiblePathElement.y;
}
if (smoothingStartIndex != -1) {
for (int j = smoothingStartIndex; j < (this.path.size() - 1); j++) {
final Point removed = this.path.remove(j);
}
}
}
}
float currentTargetX;
float currentTargetY;
if (this.path.size() < 2) {
currentTargetX = this.targetX;
currentTargetY = this.targetY;
}
else {
Point nextPathElement = this.path.get(0);
if ((this.path.size() > 2) && !this.recalculated) {
final Point secondPathElement = this.path.get(1);
if ((secondPathElement.distance(startCellX, startCellY) >= 5 /* 5 nodes */)
&& (nextPathElement.distance(startCellX, startCellY) <= 2)) {
nextPathElement = secondPathElement;
this.path.remove(0);
}
}
currentTargetX = pathingGrid.getWorldX(nextPathElement.x);
currentTargetY = pathingGrid.getWorldY(nextPathElement.y);
}
float deltaX = currentTargetX - prevX;
float deltaY = currentTargetY - prevY;
final double goalAngleRad = Math.atan2(deltaY, deltaX);
float goalAngle = (float) Math.toDegrees(goalAngleRad);
if (goalAngle < 0) {
@ -56,20 +138,61 @@ public class CMoveOrder implements COrder {
}
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.targetX);
this.unit.setY(this.targetY);
return true;
double continueDistance = speedTick;
do {
boolean done;
float nextX, nextY;
final double travelDistance = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY));
if (travelDistance <= continueDistance) {
nextX = currentTargetX;
nextY = currentTargetY;
continueDistance = continueDistance - travelDistance;
done = true;
}
else {
final double radianFacing = Math.toRadians(facing);
this.unit.setX(prevX + (float) (Math.cos(radianFacing) * speedTick));
this.unit.setY(prevY + (float) (Math.sin(radianFacing) * speedTick));
nextX = (prevX + (float) (Math.cos(radianFacing) * continueDistance));
nextY = (prevY + (float) (Math.sin(radianFacing) * continueDistance));
continueDistance = 0;
done = (pathingGrid.getCellX(nextX) == pathingGrid.getCellX(currentTargetX))
&& (pathingGrid.getCellY(nextY) == pathingGrid.getCellY(currentTargetY));
}
final short terrainPathing = pathingGrid.getPathing(nextX, nextY);
if (movementType.isPathable(terrainPathing)) {
this.unit.setX(nextX);
this.unit.setY(nextY);
if (done) {
if (this.path.isEmpty()) {
return true;
}
else {
this.path.remove(0);
if (this.path.size() < 2) {
currentTargetX = this.targetX;
currentTargetY = this.targetY;
}
else {
final Point firstPathElement = this.path.get(0);
currentTargetX = pathingGrid.getWorldX(firstPathElement.x);
currentTargetY = pathingGrid.getWorldY(firstPathElement.y);
}
deltaY = currentTargetY - prevY;
deltaX = currentTargetX - prevX;
}
}
}
else {
this.path = simulation.findNaiveSlowPath(startCellX, startCellY, goalCellX, goalCellY,
movementType);
this.recalculated = true;
if (this.path.isEmpty()) {
return true;
}
}
this.wasWithinPropWindow = true;
}
while (continueDistance > 0);
}
else {
// If this happens, the unit is facing the wrong way, and has to turn before
// moving.
@ -85,11 +208,11 @@ public class CMoveOrder implements COrder {
}
@Override
public String getAnimationName() {
public AnimationTokens.PrimaryTag getAnimationName() {
if (!this.wasWithinPropWindow) {
return "stand";
return AnimationTokens.PrimaryTag.STAND;
}
return "walk";
return AnimationTokens.PrimaryTag.WALK;
}
}

View File

@ -0,0 +1,156 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing;
import java.awt.Point;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
public class CPathfindingProcessor {
private final PathingGrid pathingGrid;
private final Node[][] nodes;
private Node goal;
public CPathfindingProcessor(final PathingGrid pathingGrid) {
this.pathingGrid = pathingGrid;
this.nodes = new Node[pathingGrid.getHeight()][pathingGrid.getWidth()];
for (int i = 0; i < this.nodes.length; i++) {
for (int j = 0; j < this.nodes[i].length; j++) {
this.nodes[i][j] = new Node(new Point(j, i));
}
}
}
/**
* Finds the path to a point using a naive, slow, and unoptimized algorithm.
* Does not have optimizations yet, do this for a bunch of units and it will
* probably lag like a walrus. The implementation here was created by reading
* the wikipedia article on A* to jog my memory from data structures class back
* in college, and is meant only as a first draft to get things working.
*
*
* @param start
* @param goal
* @return
*/
public List<Point> findNaiveSlowPath(final int startX, final int startY, final int goalX, final int goalY,
final PathingGrid.MovementType movementType) {
if ((startX == goalX) && (startY == goalY)) {
return Collections.emptyList();
}
this.goal = this.nodes[goalY][goalX];
final Node start = this.nodes[startY][startX];
for (int i = 0; i < this.nodes.length; i++) {
for (int j = 0; j < this.nodes[i].length; j++) {
this.nodes[i][j].g = Float.POSITIVE_INFINITY;
this.nodes[i][j].f = Float.POSITIVE_INFINITY;
this.nodes[i][j].cameFrom = null;
}
}
start.g = 0;
start.f = h(start);
final PriorityQueue<Node> openSet = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(final Node a, final Node b) {
return Double.compare(f(a), f(b));
}
});
openSet.add(start);
while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current == this.goal) {
final LinkedList<Point> totalPath = new LinkedList<>();
Direction lastCameFromDirection = null;
while (current.cameFrom != null) {
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)) {
totalPath.addFirst(current.point);
lastCameFromDirection = current.cameFromDirection;
}
current = current.cameFrom;
}
return totalPath;
}
for (final Direction direction : Direction.VALUES) {
final int x = current.point.x + direction.xOffset;
final int y = current.point.y + direction.yOffset;
if ((x >= 0) && (x < this.pathingGrid.getWidth()) && (y >= 0) && (y < this.pathingGrid.getHeight())
&& movementType.isPathable(this.pathingGrid.getCellPathing(x, y))
&& movementType.isPathable(this.pathingGrid.getCellPathing(current.point.x, y))
&& movementType.isPathable(this.pathingGrid.getCellPathing(x, current.point.y))) {
double turnCost;
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
turnCost = 0.25;
}
else {
turnCost = 0;
}
final double tentativeScore = current.g + direction.length + turnCost;
final Node neighbor = this.nodes[y][x];
if (tentativeScore < neighbor.g) {
neighbor.cameFrom = current;
neighbor.cameFromDirection = direction;
neighbor.g = tentativeScore;
neighbor.f = tentativeScore + h(neighbor);
if (!openSet.contains(neighbor)) {
openSet.add(neighbor);
}
}
}
}
}
return Collections.emptyList();
}
public double f(final Node n) {
return n.g + h(n);
}
public double g(final Node n) {
return n.g;
}
public float h(final Node n) {
return (float) n.point.distance(this.goal.point);
}
public static final class Node {
public Direction cameFromDirection;
private final Point point;
private double f;
private double g;
private Node cameFrom;
private Node(final Point point) {
this.point = point;
}
}
private static enum Direction {
NORTH_WEST(-1, 1),
NORTH(0, 1),
NORTH_EAST(1, 1),
EAST(1, 0),
SOUTH_EAST(1, -1),
SOUTH(0, -1),
SOUTH_WEST(-1, -1),
WEST(-1, 0);
public static final Direction[] VALUES = values();
private final int xOffset;
private final int yOffset;
private final double length;
private Direction(final int xOffset, final int yOffset) {
this.xOffset = xOffset;
this.yOffset = yOffset;
final double sqrt = Math.sqrt((xOffset * xOffset) + (yOffset * yOffset));
this.length = sqrt;
}
}
}

View File

@ -7,7 +7,7 @@ import org.lwjgl.opengl.GL33;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.etheller.warsmash.WarsmashGdxGame;
import com.etheller.warsmash.WarsmashGdxMapGame;
import com.etheller.warsmash.viewer5.gl.ANGLEInstancedArrays;
import com.etheller.warsmash.viewer5.gl.DynamicShadowExtension;
import com.etheller.warsmash.viewer5.gl.Extensions;
@ -58,10 +58,10 @@ public class DesktopLauncher {
config.gles30ContextMajorVersion = 3;
config.gles30ContextMinorVersion = 3;
config.samples = 16;
// config.fullscreen = false;
// config.fullscreen = true;
// final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode();
// config.width = desktopDisplayMode.width;
// config.height = desktopDisplayMode.height;
new LwjglApplication(new WarsmashGdxGame(), config);
new LwjglApplication(new WarsmashGdxMapGame(), config);
}
}