mirror of
https://github.com/Retera/WarsmashModEngine.git
synced 2022-07-31 17:38:59 +02:00
Update to include race, basic target types, better combat UI and behaviors
This commit is contained in:
parent
bf16b4f698
commit
cfa5f952de
@ -1,15 +1,23 @@
|
|||||||
[DataSources]
|
[DataSources]
|
||||||
Count=5
|
Count=7
|
||||||
Type00=Folder
|
Type00=MPQ
|
||||||
Path00="E:\Backups\Warcraft III 1.30 but dead\War3mod.mpq"
|
Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq"
|
||||||
Type01=Folder
|
Type01=MPQ
|
||||||
Path01="E:\Backups\Warcraft\Data\127"
|
Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq"
|
||||||
Type02=Folder
|
Type02=MPQ
|
||||||
Path02="..\..\resources"
|
Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq"
|
||||||
Type03=Folder
|
Type03=MPQ
|
||||||
Path03="E:\Backups\Warsmash\Data"
|
Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq"
|
||||||
Type04=Folder
|
Type04=Folder
|
||||||
Path04="."
|
Path04="..\..\resources"
|
||||||
|
Type05=Folder
|
||||||
|
Path05="E:\Backups\Warsmash\Data"
|
||||||
|
Type06=Folder
|
||||||
|
Path06="."
|
||||||
|
|
||||||
[Map]
|
[Map]
|
||||||
FilePath="PitchRoll.w3x"
|
//FilePath="CombatUnitTests.w3x"
|
||||||
|
FilePath="PitchRoll.w3x"
|
||||||
|
//FilePath="PlayerPeasants.w3m"
|
||||||
|
//FilePath="FireLord.w3x"
|
||||||
|
//FilePath="Maps\Campaign\NightElf03.w3m"
|
17
core/assets/warsmash131.ini
Normal file
17
core/assets/warsmash131.ini
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[DataSources]
|
||||||
|
Count=5
|
||||||
|
Type00=Folder
|
||||||
|
Path00="E:\Games\Warcraft III CASC 1.31\war3.w3mod"
|
||||||
|
Type01=Folder
|
||||||
|
Path01="E:\Games\Warcraft III CASC 1.31\war3.w3mod\_locales\enus.w3mod"
|
||||||
|
Type02=Folder
|
||||||
|
Path02="..\..\resources"
|
||||||
|
Type03=Folder
|
||||||
|
Path03="E:\Backups\Warsmash\Data"
|
||||||
|
Type04=Folder
|
||||||
|
Path04="."
|
||||||
|
|
||||||
|
[Map]
|
||||||
|
FilePath="PitchRoll.w3x"
|
||||||
|
//FilePath="ReforgedGeorgeVacation.w3x"
|
||||||
|
//FilePath="Maps\Campaign\NightElf03.w3m"
|
@ -7,7 +7,6 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
import com.badlogic.gdx.ApplicationAdapter;
|
import com.badlogic.gdx.ApplicationAdapter;
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import com.badlogic.gdx.audio.Music;
|
|
||||||
import com.badlogic.gdx.graphics.GL20;
|
import com.badlogic.gdx.graphics.GL20;
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||||
@ -19,7 +18,6 @@ import com.etheller.warsmash.datasources.DataSource;
|
|||||||
import com.etheller.warsmash.datasources.DataSourceDescriptor;
|
import com.etheller.warsmash.datasources.DataSourceDescriptor;
|
||||||
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
|
import com.etheller.warsmash.datasources.FolderDataSourceDescriptor;
|
||||||
import com.etheller.warsmash.parsers.mdlx.Sequence;
|
import com.etheller.warsmash.parsers.mdlx.Sequence;
|
||||||
import com.etheller.warsmash.util.DataSourceFileHandle;
|
|
||||||
import com.etheller.warsmash.viewer5.Camera;
|
import com.etheller.warsmash.viewer5.Camera;
|
||||||
import com.etheller.warsmash.viewer5.CanvasProvider;
|
import com.etheller.warsmash.viewer5.CanvasProvider;
|
||||||
import com.etheller.warsmash.viewer5.ModelViewer;
|
import com.etheller.warsmash.viewer5.ModelViewer;
|
||||||
@ -29,6 +27,7 @@ import com.etheller.warsmash.viewer5.SolvedPath;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
|
||||||
|
|
||||||
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
|
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
|
||||||
private static final boolean SPIN = false;
|
private static final boolean SPIN = false;
|
||||||
@ -68,21 +67,21 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
this.viewer.addHandler(new MdxHandler());
|
this.viewer.addHandler(new MdxHandler());
|
||||||
this.viewer.enableAudio();
|
this.viewer.enableAudio();
|
||||||
|
|
||||||
final Scene scene = this.viewer.addWorldScene();
|
final Scene scene = this.viewer.addSimpleScene();
|
||||||
// scene.enableAudio();
|
scene.enableAudio();
|
||||||
|
|
||||||
this.cameraManager = new CameraManager();
|
this.cameraManager = new CameraManager();
|
||||||
this.cameraManager.setupCamera(scene);
|
this.cameraManager.setupCamera(scene);
|
||||||
|
|
||||||
// this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx",
|
// this.mainModel = (MdxModel) this.viewer.load("Doodads\\Cinematic\\ArthasIllidanFight\\ArthasIllidanFight.mdx",
|
||||||
this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx",
|
// this.mainModel = (MdxModel) this.viewer.load("UI\\Glues\\SinglePlayer\\NightElf_Exp\\NightElf_Exp.mdx",
|
||||||
// this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx",
|
// this.mainModel = (MdxModel) this.viewer.load("Abilities\\Spells\\Orc\\FeralSpirit\\feralspirittarget.mdx",
|
||||||
new PathSolver() {
|
// new PathSolver() {
|
||||||
@Override
|
// @Override
|
||||||
public SolvedPath solve(final String src, final Object solverParams) {
|
// public SolvedPath solve(final String src, final Object solverParams) {
|
||||||
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
|
// return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
|
||||||
}
|
// }
|
||||||
}, null);
|
// }, null);
|
||||||
|
|
||||||
// final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1);
|
// final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1);
|
||||||
// for (final Sequence seq : this.mainModel.getSequences()) {
|
// for (final Sequence seq : this.mainModel.getSequences()) {
|
||||||
@ -91,18 +90,20 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
// System.out.println(Arrays.toString(evt.keyFrames));
|
// System.out.println(Arrays.toString(evt.keyFrames));
|
||||||
// System.out.println(evt.name);
|
// System.out.println(evt.name);
|
||||||
|
|
||||||
this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);
|
// this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);
|
||||||
|
|
||||||
this.mainInstance.setScene(scene);
|
// this.mainInstance.setScene(scene);
|
||||||
|
//
|
||||||
final int animIndex = 0;
|
// final int animIndex = 0;
|
||||||
this.modelCamera = this.mainModel.cameras.get(animIndex);
|
// this.modelCamera = this.mainModel.cameras.get(animIndex);
|
||||||
this.mainInstance.setSequence(animIndex);
|
// this.mainInstance.setSequence(animIndex);
|
||||||
|
//
|
||||||
this.mainInstance.setSequenceLoopMode(4);
|
// this.mainInstance.setSequenceLoopMode(SequenceLoopMode.LOOP_TO_NEXT_ANIMATION);
|
||||||
|
|
||||||
// acolytesHarvestingSceneJoke2(scene);
|
// acolytesHarvestingSceneJoke2(scene);
|
||||||
|
|
||||||
|
singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth");
|
||||||
|
|
||||||
System.out.println("Loaded");
|
System.out.println("Loaded");
|
||||||
Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background
|
Gdx.gl30.glClearColor(0.5f, 0.5f, 0.5f, 1); // TODO remove white background
|
||||||
|
|
||||||
@ -141,7 +142,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
}
|
}
|
||||||
instance3.setSequence(animIndex);
|
instance3.setSequence(animIndex);
|
||||||
|
|
||||||
instance3.setSequenceLoopMode(2);
|
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void singleModelScene(final Scene scene, final String path, final String animName) {
|
private void singleModelScene(final Scene scene, final String path, final String animName) {
|
||||||
@ -160,11 +161,12 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
for (final Sequence s : model2.getSequences()) {
|
for (final Sequence s : model2.getSequences()) {
|
||||||
if (s.getName().toLowerCase().startsWith(animName)) {
|
if (s.getName().toLowerCase().startsWith(animName)) {
|
||||||
animIndex = model2.getSequences().indexOf(s);
|
animIndex = model2.getSequences().indexOf(s);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
instance3.setSequence(animIndex);
|
instance3.setSequence(animIndex);
|
||||||
|
|
||||||
instance3.setSequenceLoopMode(2);
|
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acolytesHarvestingScene(final Scene scene) {
|
private void acolytesHarvestingScene(final Scene scene) {
|
||||||
@ -196,7 +198,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
}
|
}
|
||||||
acolyteInstance.setSequence(animIndex);
|
acolyteInstance.setSequence(animIndex);
|
||||||
|
|
||||||
acolyteInstance.setSequenceLoopMode(2);
|
acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
|
|
||||||
final double angle = ((Math.PI * 2) / 5) * i;
|
final double angle = ((Math.PI * 2) / 5) * i;
|
||||||
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
||||||
@ -209,7 +211,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
|
|
||||||
effectInstance.setSequence(1);
|
effectInstance.setSequence(1);
|
||||||
|
|
||||||
effectInstance.setSequenceLoopMode(2);
|
effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
||||||
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
|
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
|
||||||
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
|
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
|
||||||
@ -228,7 +230,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
|
|
||||||
mineInstance.setSequence(2);
|
mineInstance.setSequence(2);
|
||||||
|
|
||||||
mineInstance.setSequenceLoopMode(2);
|
mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acolytesHarvestingSceneJoke2(final Scene scene) {
|
private void acolytesHarvestingSceneJoke2(final Scene scene) {
|
||||||
@ -260,7 +262,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
}
|
}
|
||||||
acolyteInstance.setSequence(animIndex);
|
acolyteInstance.setSequence(animIndex);
|
||||||
|
|
||||||
acolyteInstance.setSequenceLoopMode(2);
|
acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
|
|
||||||
final double angle = ((Math.PI * 2) / 5) * i;
|
final double angle = ((Math.PI * 2) / 5) * i;
|
||||||
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
||||||
@ -273,7 +275,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
|
|
||||||
effectInstance.setSequence(1);
|
effectInstance.setSequence(1);
|
||||||
|
|
||||||
effectInstance.setSequenceLoopMode(2);
|
effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
|
||||||
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
|
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
|
||||||
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
|
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
|
||||||
@ -295,7 +297,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
mineInstance.localScale.y = 2;
|
mineInstance.localScale.y = 2;
|
||||||
mineInstance.localScale.z = 2;
|
mineInstance.localScale.z = 2;
|
||||||
|
|
||||||
mineInstance.setSequenceLoopMode(2);
|
mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
final MdxModel mineModel2 = (MdxModel) this.viewer
|
final MdxModel mineModel2 = (MdxModel) this.viewer
|
||||||
.load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() {
|
.load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() {
|
||||||
@Override
|
@Override
|
||||||
@ -309,7 +311,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
|
|
||||||
mineInstance2.setSequence(0);
|
mineInstance2.setSequence(0);
|
||||||
|
|
||||||
mineInstance2.setSequenceLoopMode(2);
|
mineInstance2.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void makeFourHundred(final Scene scene, final MdxModel model2) {
|
private void makeFourHundred(final Scene scene, final MdxModel model2) {
|
||||||
@ -323,7 +325,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
final int animIndex = i % model2.getSequences().size();
|
final int animIndex = i % model2.getSequences().size();
|
||||||
instance3.setSequence(animIndex);
|
instance3.setSequence(animIndex);
|
||||||
|
|
||||||
instance3.setSequenceLoopMode(2);
|
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +341,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
final int animIndex = i % model2.getSequences().size();
|
final int animIndex = i % model2.getSequences().size();
|
||||||
instance3.setSequence(animIndex);
|
instance3.setSequence(animIndex);
|
||||||
|
|
||||||
instance3.setSequenceLoopMode(2);
|
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,13 +355,13 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera;
|
private com.etheller.warsmash.viewer5.handlers.mdx.Camera modelCamera;
|
||||||
private final float[] cameraPositionTemp = new float[3];
|
private final float[] cameraPositionTemp = new float[3];
|
||||||
private final float[] cameraTargetTemp = new float[3];
|
private final float[] cameraTargetTemp = new float[3];
|
||||||
private boolean firstFrame = true;
|
private final boolean firstFrame = true;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render() {
|
public void render() {
|
||||||
Gdx.gl30.glBindVertexArray(VAO);
|
Gdx.gl30.glBindVertexArray(VAO);
|
||||||
if (SPIN) {
|
if (SPIN) {
|
||||||
this.cameraManager.horizontalAngle += 0.01;
|
this.cameraManager.horizontalAngle += 0.0001;
|
||||||
if (this.cameraManager.horizontalAngle > (2 * Math.PI)) {
|
if (this.cameraManager.horizontalAngle > (2 * Math.PI)) {
|
||||||
this.cameraManager.horizontalAngle = 0;
|
this.cameraManager.horizontalAngle = 0;
|
||||||
}
|
}
|
||||||
@ -384,14 +386,14 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
this.mainInstance.setSequence(sequence);
|
this.mainInstance.setSequence(sequence);
|
||||||
this.mainInstance.frame += (int) (Gdx.graphics.getRawDeltaTime() * 1000);
|
this.mainInstance.frame += (int) (Gdx.graphics.getRawDeltaTime() * 1000);
|
||||||
}
|
}
|
||||||
if (this.firstFrame) {
|
// if (this.firstFrame) {
|
||||||
final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource,
|
// final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource,
|
||||||
"Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3"));
|
// "Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3"));
|
||||||
music.setVolume(0.2f);
|
// music.setVolume(0.2f);
|
||||||
music.setLooping(true);
|
// music.setLooping(true);
|
||||||
music.play();
|
// music.play();
|
||||||
this.firstFrame = false;
|
// this.firstFrame = false;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -445,7 +447,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
this.zoomFactor = 0.1f;
|
this.zoomFactor = 0.1f;
|
||||||
this.horizontalAngle = (float) (Math.PI / 2);
|
this.horizontalAngle = (float) (Math.PI / 2);
|
||||||
this.verticalAngle = (float) (Math.PI / 4);
|
this.verticalAngle = (float) (Math.PI / 4);
|
||||||
this.distance = 1000;
|
this.distance = 500;
|
||||||
this.position = new Vector3();
|
this.position = new Vector3();
|
||||||
this.target = new Vector3(0, 0, 50);
|
this.target = new Vector3(0, 0, 50);
|
||||||
this.worldUp = new Vector3(0, 0, 1);
|
this.worldUp = new Vector3(0, 0, 1);
|
||||||
@ -503,6 +505,9 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
|
|||||||
WarsmashGdxGame.this.modelCamera.nearClippingPlane,
|
WarsmashGdxGame.this.modelCamera.nearClippingPlane,
|
||||||
WarsmashGdxGame.this.modelCamera.farClippingPlane);
|
WarsmashGdxGame.this.modelCamera.farClippingPlane);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
this.camera.perspective(70, this.camera.getAspect(), 100, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
|
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,7 @@ import com.badlogic.gdx.math.Quaternion;
|
|||||||
import com.badlogic.gdx.math.Rectangle;
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
import com.badlogic.gdx.math.Vector2;
|
import com.badlogic.gdx.math.Vector2;
|
||||||
import com.badlogic.gdx.math.Vector3;
|
import com.badlogic.gdx.math.Vector3;
|
||||||
import com.badlogic.gdx.utils.viewport.FitViewport;
|
import com.badlogic.gdx.utils.viewport.ExtendViewport;
|
||||||
import com.badlogic.gdx.utils.viewport.Viewport;
|
|
||||||
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
|
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
|
||||||
import com.etheller.warsmash.datasources.DataSource;
|
import com.etheller.warsmash.datasources.DataSource;
|
||||||
import com.etheller.warsmash.datasources.DataSourceDescriptor;
|
import com.etheller.warsmash.datasources.DataSourceDescriptor;
|
||||||
@ -51,12 +50,9 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds;
|
import com.etheller.warsmash.viewer5.handlers.mdx.ReplaceableIds;
|
||||||
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
|
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset.UnitAckSound;
|
import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon;
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop;
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI;
|
import com.etheller.warsmash.viewer5.handlers.w3x.ui.MeleeUI;
|
||||||
|
|
||||||
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
|
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
|
||||||
@ -74,18 +70,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
// libGDX stuff
|
// libGDX stuff
|
||||||
private OrthographicCamera uiCamera;
|
private OrthographicCamera uiCamera;
|
||||||
private BitmapFont font;
|
private BitmapFont font;
|
||||||
private BitmapFont font24;
|
|
||||||
private BitmapFont font20;
|
private BitmapFont font20;
|
||||||
private SpriteBatch batch;
|
private SpriteBatch batch;
|
||||||
private Viewport uiViewport;
|
private ExtendViewport uiViewport;
|
||||||
private GlyphLayout glyphLayout;
|
private GlyphLayout glyphLayout;
|
||||||
|
|
||||||
private Texture consoleUITexture;
|
private Texture consoleUITexture;
|
||||||
private RenderUnit selectedUnit;
|
|
||||||
private int selectedSoundCount = 0;
|
private int selectedSoundCount = 0;
|
||||||
|
|
||||||
private Texture activeButtonTexture;
|
|
||||||
|
|
||||||
private Rectangle minimap;
|
private Rectangle minimap;
|
||||||
private Rectangle minimapFilledArea;
|
private Rectangle minimapFilledArea;
|
||||||
|
|
||||||
@ -171,20 +163,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
final float w = Gdx.graphics.getWidth();
|
final float w = Gdx.graphics.getWidth();
|
||||||
final float h = Gdx.graphics.getHeight();
|
final float h = Gdx.graphics.getHeight();
|
||||||
|
|
||||||
this.tempRect.x = 0;
|
|
||||||
this.tempRect.y = 0;
|
|
||||||
this.tempRect.width = w;
|
|
||||||
this.tempRect.height = h;
|
|
||||||
this.uiScene.camera.viewport(this.tempRect);
|
|
||||||
this.uiScene.camera.ortho(0, 0.8f, 0, 0.6f, 0, 1);
|
|
||||||
|
|
||||||
final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator(
|
final FreeTypeFontGenerator fontGenerator = new FreeTypeFontGenerator(
|
||||||
new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF"));
|
new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF"));
|
||||||
final FreeTypeFontParameter fontParam = new FreeTypeFontParameter();
|
final FreeTypeFontParameter fontParam = new FreeTypeFontParameter();
|
||||||
fontParam.size = 32;
|
fontParam.size = 32;
|
||||||
this.font = fontGenerator.generateFont(fontParam);
|
this.font = fontGenerator.generateFont(fontParam);
|
||||||
fontParam.size = 24;
|
|
||||||
this.font24 = fontGenerator.generateFont(fontParam);
|
|
||||||
fontParam.size = 20;
|
fontParam.size = 20;
|
||||||
this.font20 = fontGenerator.generateFont(fontParam);
|
this.font20 = fontGenerator.generateFont(fontParam);
|
||||||
this.glyphLayout = new GlyphLayout();
|
this.glyphLayout = new GlyphLayout();
|
||||||
@ -193,10 +176,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
// height
|
// height
|
||||||
// Height is multiplied by aspect ratio.
|
// Height is multiplied by aspect ratio.
|
||||||
this.uiCamera = new OrthographicCamera();
|
this.uiCamera = new OrthographicCamera();
|
||||||
this.uiViewport = new FitViewport(1600, 1200, this.uiCamera);
|
this.uiViewport = new ExtendViewport(1600, 1200, this.uiCamera);
|
||||||
this.uiViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
|
this.uiViewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
|
||||||
|
|
||||||
this.uiCamera.position.set(this.uiCamera.viewportWidth / 2f, this.uiCamera.viewportHeight / 2f, 0);
|
this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0);
|
||||||
this.uiCamera.update();
|
this.uiCamera.update();
|
||||||
|
|
||||||
this.batch = new SpriteBatch();
|
this.batch = new SpriteBatch();
|
||||||
@ -223,9 +206,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeButtonTexture = ImageUtils.getBLPTexture(this.viewer.dataSource,
|
|
||||||
"UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp");
|
|
||||||
|
|
||||||
for (int i = 0; i < this.teamColors.length; i++) {
|
for (int i = 0; i < this.teamColors.length; i++) {
|
||||||
this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource,
|
this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource,
|
||||||
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp");
|
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp");
|
||||||
@ -236,8 +216,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
|
|
||||||
Gdx.input.setInputProcessor(this);
|
Gdx.input.setInputProcessor(this);
|
||||||
|
|
||||||
// final Music music = Gdx.audio.newMusic(
|
// final Music music = Gdx.audio
|
||||||
// new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\War2IntroMusic.mp3"));
|
// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\Undead2.mp3"));
|
||||||
// music.setVolume(0.2f);
|
// music.setVolume(0.2f);
|
||||||
// music.setLooping(true);
|
// music.setLooping(true);
|
||||||
// music.play();
|
// music.play();
|
||||||
@ -277,6 +257,27 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
});
|
});
|
||||||
this.meleeUI.main();
|
this.meleeUI.main();
|
||||||
fontGenerator.dispose();
|
fontGenerator.dispose();
|
||||||
|
|
||||||
|
updateUIScene();
|
||||||
|
|
||||||
|
this.meleeUI.resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUIScene() {
|
||||||
|
this.tempRect.x = this.uiViewport.getScreenX();
|
||||||
|
this.tempRect.y = this.uiViewport.getScreenY();
|
||||||
|
this.tempRect.width = this.uiViewport.getScreenWidth();
|
||||||
|
this.tempRect.height = this.uiViewport.getScreenHeight();
|
||||||
|
this.uiScene.camera.viewport(this.tempRect);
|
||||||
|
final float worldWidth = this.uiViewport.getWorldWidth();
|
||||||
|
final float worldHeight = this.uiViewport.getWorldHeight();
|
||||||
|
final float xScale = worldWidth / this.uiViewport.getMinWorldWidth();
|
||||||
|
final float yScale = worldHeight / this.uiViewport.getMinWorldHeight();
|
||||||
|
final float uiSceneWidth = 0.8f * xScale;
|
||||||
|
final float uiSceneHeight = 0.6f * yScale;
|
||||||
|
final float uiSceneX = ((0.8f - uiSceneWidth) / 2);
|
||||||
|
final float uiSceneY = ((0.6f - uiSceneHeight) / 2);
|
||||||
|
this.uiScene.camera.ortho(uiSceneX, uiSceneWidth + uiSceneX, uiSceneY, uiSceneHeight + uiSceneY, -1f, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -313,32 +314,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
this.font.setColor(Color.YELLOW);
|
this.font.setColor(Color.YELLOW);
|
||||||
final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond();
|
final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond();
|
||||||
this.glyphLayout.setText(this.font, fpsString);
|
this.glyphLayout.setText(this.font, fpsString);
|
||||||
this.font.draw(this.batch, fpsString, (this.uiViewport.getWorldWidth() - this.glyphLayout.width) / 2, 1100);
|
this.font.draw(this.batch, fpsString, (this.uiViewport.getMinWorldWidth() - this.glyphLayout.width) / 2, 1100);
|
||||||
|
|
||||||
this.batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height);
|
this.batch.draw(this.minimapTexture, this.minimap.x, this.minimap.y, this.minimap.width, this.minimap.height);
|
||||||
|
|
||||||
if (this.selectedUnit != null) {
|
final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea();
|
||||||
int messageIndex = 0;
|
|
||||||
for (final Message message : this.messages) {
|
|
||||||
this.font20.draw(this.batch, message.text, 100, 400 + (25 * (messageIndex++)));
|
|
||||||
}
|
|
||||||
this.font20.setColor(Color.WHITE);
|
|
||||||
|
|
||||||
final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder();
|
|
||||||
for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) {
|
|
||||||
this.batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()),
|
|
||||||
190 - (88 * commandCardIcon.getY()), 78f, 78f);
|
|
||||||
if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId()))
|
|
||||||
|| ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) {
|
|
||||||
final int blendDstFunc = this.batch.getBlendDstFunc();
|
|
||||||
final int blendSrcFunc = this.batch.getBlendSrcFunc();
|
|
||||||
this.batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
|
|
||||||
this.batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()),
|
|
||||||
190 - (88 * commandCardIcon.getY()), 78f, 78f);
|
|
||||||
this.batch.setBlendFunction(blendSrcFunc, blendDstFunc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final RenderUnit unit : this.viewer.units) {
|
for (final RenderUnit unit : this.viewer.units) {
|
||||||
if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) {
|
if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) {
|
||||||
System.err.println(unit.row.getName() + " at ( " + unit.location[0] + ", " + unit.location[1] + " )"
|
System.err.println(unit.row.getName() + " at ( " + unit.location[0] + ", " + unit.location[1] + " )"
|
||||||
@ -347,10 +327,12 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
}
|
}
|
||||||
final Texture minimapIcon = this.teamColors[unit.playerIndex];
|
final Texture minimapIcon = this.teamColors[unit.playerIndex];
|
||||||
this.batch.draw(minimapIcon,
|
this.batch.draw(minimapIcon,
|
||||||
this.minimapFilledArea.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0])
|
this.minimapFilledArea.x
|
||||||
/ ((this.viewer.terrain.columns - 1) * 128f)) * this.minimapFilledArea.width),
|
+ (((unit.location[0] - playableMapArea.getX()) / (playableMapArea.getWidth()))
|
||||||
this.minimapFilledArea.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1])
|
* this.minimapFilledArea.width),
|
||||||
/ ((this.viewer.terrain.rows - 1) * 128f)) * this.minimapFilledArea.height),
|
this.minimapFilledArea.y
|
||||||
|
+ (((unit.location[1] - playableMapArea.getY()) / (playableMapArea.getHeight()))
|
||||||
|
* this.minimapFilledArea.height),
|
||||||
4, 4);
|
4, 4);
|
||||||
}
|
}
|
||||||
this.batch.end();
|
this.batch.end();
|
||||||
@ -411,14 +393,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
final float portraitTestHeight = (100 / 480f) * height;
|
final float portraitTestHeight = (100 / 480f) * height;
|
||||||
|
|
||||||
this.uiViewport.update(width, height);
|
this.uiViewport.update(width, height);
|
||||||
this.uiCamera.position.set(this.uiCamera.viewportWidth / 2, this.uiCamera.viewportHeight / 2, 0);
|
this.uiCamera.position.set(this.uiViewport.getMinWorldWidth() / 2, this.uiViewport.getMinWorldHeight() / 2, 0);
|
||||||
|
|
||||||
this.tempRect.x = this.uiViewport.getScreenX();
|
updateUIScene();
|
||||||
this.tempRect.y = this.uiViewport.getScreenY();
|
|
||||||
this.tempRect.width = this.uiViewport.getScreenWidth();
|
|
||||||
this.tempRect.height = this.uiViewport.getScreenHeight();
|
|
||||||
this.uiScene.camera.viewport(this.tempRect);
|
|
||||||
this.uiScene.camera.ortho(0f, 0.8f, 0f, 0.6f, -1f, 1);
|
|
||||||
|
|
||||||
this.meleeUI.resize();
|
this.meleeUI.resize();
|
||||||
}
|
}
|
||||||
@ -621,20 +598,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
clickLocationTemp2.y = screenY;
|
clickLocationTemp2.y = screenY;
|
||||||
this.uiViewport.unproject(clickLocationTemp2);
|
this.uiViewport.unproject(clickLocationTemp2);
|
||||||
|
|
||||||
if (this.selectedUnit != null) {
|
|
||||||
for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) {
|
|
||||||
if (new Rectangle(1235 + (86.8f * commandCardIcon.getX()), 190 - (88 * commandCardIcon.getY()), 78f,
|
|
||||||
78f).contains(clickLocationTemp2)) {
|
|
||||||
if (button == Input.Buttons.RIGHT) {
|
|
||||||
this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Right mouse click"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.messages.add(new Message(Gdx.input.getCurrentEventTime(), "Left mouse click"));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) {
|
if (this.minimapFilledArea.contains(clickLocationTemp2.x, clickLocationTemp2.y)) {
|
||||||
final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width;
|
final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width;
|
||||||
final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height;
|
final float clickY = (clickLocationTemp2.y - this.minimapFilledArea.y) / this.minimapFilledArea.height;
|
||||||
@ -646,24 +609,32 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
}
|
}
|
||||||
if (button == Input.Buttons.RIGHT) {
|
if (button == Input.Buttons.RIGHT) {
|
||||||
final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY);
|
final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY);
|
||||||
if ((rayPickUnit != null) && (this.selectedUnit != null)
|
if (this.meleeUI.getSelectedUnit() != null) {
|
||||||
&& (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) {
|
if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.meleeUI.getSelectedUnit().playerIndex)
|
||||||
if (this.viewer.orderSmart(rayPickUnit)) {
|
&& !rayPickUnit.getSimulationUnit().isDead()) {
|
||||||
this.meleeUI.portraitTalk();
|
if (this.viewer.orderSmart(rayPickUnit)) {
|
||||||
this.selectedSoundCount = 0;
|
if (this.meleeUI.getSelectedUnit().soundset.yesAttack.playUnitResponse(
|
||||||
|
this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) {
|
||||||
|
this.meleeUI.portraitTalk();
|
||||||
|
}
|
||||||
|
this.selectedSoundCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
|
||||||
this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
|
System.out.println(clickLocationTemp);
|
||||||
System.out.println(clickLocationTemp);
|
this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0);
|
||||||
this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0);
|
final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128);
|
||||||
final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128);
|
final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128);
|
||||||
final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128);
|
System.out.println(x + "," + y);
|
||||||
System.out.println(x + "," + y);
|
this.viewer.terrain.logRomp(x, y);
|
||||||
this.viewer.terrain.logRomp(x, y);
|
if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) {
|
||||||
if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) {
|
if (this.meleeUI.getSelectedUnit().soundset.yes.playUnitResponse(
|
||||||
this.meleeUI.portraitTalk();
|
this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) {
|
||||||
this.selectedSoundCount = 0;
|
this.meleeUI.portraitTalk();
|
||||||
|
}
|
||||||
|
this.selectedSoundCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -671,14 +642,13 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
final List<RenderUnit> selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false);
|
final List<RenderUnit> selectedUnits = this.viewer.selectUnit(screenX, worldScreenY, false);
|
||||||
if (!selectedUnits.isEmpty()) {
|
if (!selectedUnits.isEmpty()) {
|
||||||
final RenderUnit unit = selectedUnits.get(0);
|
final RenderUnit unit = selectedUnits.get(0);
|
||||||
final boolean selectionChanged = this.selectedUnit != unit;
|
final boolean selectionChanged = this.meleeUI.getSelectedUnit() != unit;
|
||||||
boolean playedNewSound = false;
|
boolean playedNewSound = false;
|
||||||
if (selectionChanged) {
|
if (selectionChanged) {
|
||||||
this.selectedSoundCount = 0;
|
this.selectedSoundCount = 0;
|
||||||
}
|
}
|
||||||
this.selectedUnit = unit;
|
|
||||||
if (unit.soundset != null) {
|
if (unit.soundset != null) {
|
||||||
UnitAckSound ackSoundToPlay = unit.soundset.what;
|
UnitSound ackSoundToPlay = unit.soundset.what;
|
||||||
final int pissedSoundCount = unit.soundset.pissed.getSoundCount();
|
final int pissedSoundCount = unit.soundset.pissed.getSoundCount();
|
||||||
int soundIndex;
|
int soundIndex;
|
||||||
if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) {
|
if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) {
|
||||||
@ -688,8 +658,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
else {
|
else {
|
||||||
soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount());
|
soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount());
|
||||||
}
|
}
|
||||||
if (ackSoundToPlay.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1],
|
if (ackSoundToPlay.playUnitResponse(this.viewer.worldScene.audioContext, unit, soundIndex)) {
|
||||||
soundIndex)) {
|
|
||||||
this.selectedSoundCount++;
|
this.selectedSoundCount++;
|
||||||
if ((this.selectedSoundCount - 3) >= pissedSoundCount) {
|
if ((this.selectedSoundCount - 3) >= pissedSoundCount) {
|
||||||
this.selectedSoundCount = 0;
|
this.selectedSoundCount = 0;
|
||||||
@ -705,7 +674,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.selectedUnit = null;
|
|
||||||
this.meleeUI.selectUnit(null);
|
this.meleeUI.selectUnit(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.Texture;
|
|||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
|
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
|
||||||
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
|
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator.FreeTypeFontParameter;
|
||||||
|
import com.badlogic.gdx.utils.viewport.ExtendViewport;
|
||||||
import com.badlogic.gdx.utils.viewport.FitViewport;
|
import com.badlogic.gdx.utils.viewport.FitViewport;
|
||||||
import com.badlogic.gdx.utils.viewport.Viewport;
|
import com.badlogic.gdx.utils.viewport.Viewport;
|
||||||
import com.etheller.warsmash.datasources.DataSource;
|
import com.etheller.warsmash.datasources.DataSource;
|
||||||
@ -62,7 +63,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
|
|||||||
this.viewport = viewport;
|
this.viewport = viewport;
|
||||||
this.uiScene = uiScene;
|
this.uiScene = uiScene;
|
||||||
this.modelViewer = modelViewer;
|
this.modelViewer = modelViewer;
|
||||||
this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight());
|
if (viewport instanceof ExtendViewport) {
|
||||||
|
this.renderBounds.set(0, 0, ((ExtendViewport) viewport).getMinWorldWidth(),
|
||||||
|
((ExtendViewport) viewport).getMinWorldHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.renderBounds.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight());
|
||||||
|
}
|
||||||
this.templates = new FrameTemplateEnvironment();
|
this.templates = new FrameTemplateEnvironment();
|
||||||
this.fontGenerator = fontGenerator;
|
this.fontGenerator = fontGenerator;
|
||||||
this.fontParam = new FreeTypeFontParameter();
|
this.fontParam = new FreeTypeFontParameter();
|
||||||
@ -312,10 +319,16 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static float convertX(final Viewport viewport, final float fdfX) {
|
public static float convertX(final Viewport viewport, final float fdfX) {
|
||||||
|
if (viewport instanceof ExtendViewport) {
|
||||||
|
return (fdfX / 0.8f) * ((ExtendViewport) viewport).getMinWorldWidth();
|
||||||
|
}
|
||||||
return (fdfX / 0.8f) * viewport.getWorldWidth();
|
return (fdfX / 0.8f) * viewport.getWorldWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float convertY(final Viewport viewport, final float fdfY) {
|
public static float convertY(final Viewport viewport, final float fdfY) {
|
||||||
|
if (viewport instanceof ExtendViewport) {
|
||||||
|
return (fdfY / 0.6f) * ((ExtendViewport) viewport).getMinWorldHeight();
|
||||||
|
}
|
||||||
return (fdfY / 0.6f) * viewport.getWorldHeight();
|
return (fdfY / 0.6f) * viewport.getWorldHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,8 +338,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
|
|||||||
}
|
}
|
||||||
Texture texture = this.pathToTexture.get(path);
|
Texture texture = this.pathToTexture.get(path);
|
||||||
if (texture == null) {
|
if (texture == null) {
|
||||||
texture = ImageUtils.getBLPTexture(this.dataSource, path);
|
try {
|
||||||
this.pathToTexture.put(path, texture);
|
texture = ImageUtils.getBLPTexture(this.dataSource, path);
|
||||||
|
this.pathToTexture.put(path, texture);
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.viewport.Viewport;
|
|||||||
import com.etheller.warsmash.viewer5.Scene;
|
import com.etheller.warsmash.viewer5.Scene;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
|
||||||
|
|
||||||
public class SpriteFrame extends AbstractRenderableFrame {
|
public class SpriteFrame extends AbstractRenderableFrame {
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ public class SpriteFrame extends AbstractRenderableFrame {
|
|||||||
}
|
}
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
this.instance = (MdxComplexInstance) model.addInstance();
|
this.instance = (MdxComplexInstance) model.addInstance();
|
||||||
this.instance.setSequenceLoopMode(1);
|
this.instance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP);
|
||||||
this.instance.setScene(this.scene);
|
this.instance.setScene(this.scene);
|
||||||
this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0);
|
this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,9 @@ public class TextureFrame extends AbstractRenderableFrame {
|
|||||||
file = gameUI.getSkinField(file);
|
file = gameUI.getSkinField(file);
|
||||||
}
|
}
|
||||||
final Texture texture = gameUI.loadTexture(file);
|
final Texture texture = gameUI.loadTexture(file);
|
||||||
setTexture(texture);
|
if (texture != null) {
|
||||||
|
setTexture(texture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTexture(final Texture texture) {
|
public void setTexture(final Texture texture) {
|
||||||
|
@ -32,8 +32,8 @@ public class Player {
|
|||||||
this.allyLowPriorities = ParseUtils.readUInt32(stream);
|
this.allyLowPriorities = ParseUtils.readUInt32(stream);
|
||||||
this.allyHighPriorities = ParseUtils.readUInt32(stream);
|
this.allyHighPriorities = ParseUtils.readUInt32(stream);
|
||||||
if (version > 30) {
|
if (version > 30) {
|
||||||
enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream);
|
this.enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream);
|
||||||
enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream);
|
this.enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,4 +51,44 @@ public class Player {
|
|||||||
public int getByteLength() {
|
public int getByteLength() {
|
||||||
return 33 + this.name.length();
|
return 33 + this.name.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public War3ID getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRace() {
|
||||||
|
return this.race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIsFixedStartPosition() {
|
||||||
|
return this.isFixedStartPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getStartLocation() {
|
||||||
|
return this.startLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAllyLowPriorities() {
|
||||||
|
return this.allyLowPriorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAllyHighPriorities() {
|
||||||
|
return this.allyHighPriorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEnemyLowPrioritiesFlags() {
|
||||||
|
return this.enemyLowPrioritiesFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEnemyHighPrioritiesFlags() {
|
||||||
|
return this.enemyHighPrioritiesFlags;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,34 +38,36 @@ public class Quadtree<T> {
|
|||||||
add(node, 0);
|
add(node, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean intersectsAnythingOtherThan(final T sourceObjectToIgnore, final Rectangle bounds) {
|
public boolean intersect(final Rectangle bounds, final QuadtreeIntersector<T> intersector) {
|
||||||
if (this.leaf) {
|
if (this.leaf) {
|
||||||
for (int i = 0; i < this.nodes.size; i++) {
|
for (int i = 0; i < this.nodes.size; i++) {
|
||||||
final Node<T> node = this.nodes.get(i);
|
final Node<T> node = this.nodes.get(i);
|
||||||
if ((node.object != sourceObjectToIgnore) && node.bounds.overlaps(bounds)) {
|
if (node.bounds.overlaps(bounds)) {
|
||||||
return true;
|
if (intersector.onIntersect(node.object)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.northeast.bounds.overlaps(bounds)) {
|
if (this.northeast.bounds.overlaps(bounds)) {
|
||||||
if (this.northeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
|
if (this.northeast.intersect(bounds, intersector)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.northwest.bounds.overlaps(bounds)) {
|
if (this.northwest.bounds.overlaps(bounds)) {
|
||||||
if (this.northwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
|
if (this.northwest.intersect(bounds, intersector)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.southwest.bounds.overlaps(bounds)) {
|
if (this.southwest.bounds.overlaps(bounds)) {
|
||||||
if (this.southwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
|
if (this.southwest.intersect(bounds, intersector)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.southeast.bounds.overlaps(bounds)) {
|
if (this.southeast.bounds.overlaps(bounds)) {
|
||||||
if (this.southeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
|
if (this.southeast.intersect(bounds, intersector)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
core/src/com/etheller/warsmash/util/QuadtreeIntersector.java
Normal file
13
core/src/com/etheller/warsmash/util/QuadtreeIntersector.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.etheller.warsmash.util;
|
||||||
|
|
||||||
|
public interface QuadtreeIntersector<T> {
|
||||||
|
/**
|
||||||
|
* Handles what to do when the intersector finds an intersecting object,
|
||||||
|
* returning true if we should stop the intersection test and process no more
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* @param intersectingObject
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean onIntersect(T intersectingObject);
|
||||||
|
}
|
@ -3,7 +3,6 @@ package com.etheller.warsmash.viewer5;
|
|||||||
public class AudioContext {
|
public class AudioContext {
|
||||||
private boolean running = false;
|
private boolean running = false;
|
||||||
public Listener listener = new Listener();
|
public Listener listener = new Listener();
|
||||||
public long lastUnitResponseEndTimeMillis;
|
|
||||||
public AudioDestination destination = new AudioDestination() {
|
public AudioDestination destination = new AudioDestination() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ public class AttachmentInstance implements UpdatableObject {
|
|||||||
final MdxModel internalModel = attachment.internalModel;
|
final MdxModel internalModel = attachment.internalModel;
|
||||||
final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance();
|
final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance();
|
||||||
|
|
||||||
internalInstance.setSequenceLoopMode(2);
|
internalInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
internalInstance.dontInheritScaling = false;
|
internalInstance.dontInheritScaling = false;
|
||||||
internalInstance.hide();
|
internalInstance.hide();
|
||||||
internalInstance.setParent(instance.nodes[attachment.objectId]);
|
internalInstance.setParent(instance.nodes[attachment.objectId]);
|
||||||
|
@ -279,20 +279,27 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
|
|||||||
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
|
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
|
||||||
final GenericResource[] resources = new GenericResource[fileNames.length];
|
final GenericResource[] resources = new GenericResource[fileNames.length];
|
||||||
for (int i = 0; i < fileNames.length; i++) {
|
for (int i = 0; i < fileNames.length; i++) {
|
||||||
final String pathString = pathSolver.solve(
|
final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i];
|
||||||
((String) animSoundsRow.get("DirectoryBase")) + fileNames[i],
|
try {
|
||||||
model.solverParams).finalSrc;
|
final String pathString = pathSolver.solve(path, model.solverParams).finalSrc;
|
||||||
final GenericResource genericResource = viewer.loadGeneric(pathString,
|
final GenericResource genericResource = viewer.loadGeneric(pathString,
|
||||||
FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString));
|
FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString));
|
||||||
if (genericResource == null) {
|
if (genericResource == null) {
|
||||||
throw new IllegalStateException("Null sound: " + fileNames[i]);
|
System.err.println("Null sound: " + fileNames[i]);
|
||||||
|
}
|
||||||
|
resources[i] = genericResource;
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
System.err.println("Failed to load sound: " + path);
|
||||||
|
exc.printStackTrace();
|
||||||
}
|
}
|
||||||
resources[i] = genericResource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO JS async removed
|
// TODO JS async removed
|
||||||
for (final GenericResource resource : resources) {
|
for (final GenericResource resource : resources) {
|
||||||
this.decodedBuffers.add((Sound) resource.data);
|
if (resource != null) {
|
||||||
|
this.decodedBuffers.add((Sound) resource.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.ok = true;
|
this.ok = true;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ public class EventObjectSnd extends EmittedObject<MdxComplexInstance, EventObjec
|
|||||||
final MdxNode node = instance.nodes[emitterObject.index];
|
final MdxNode node = instance.nodes[emitterObject.index];
|
||||||
final AudioContext audioContext = scene.audioContext;
|
final AudioContext audioContext = scene.audioContext;
|
||||||
final List<Sound> decodedBuffers = emitterObject.decodedBuffers;
|
final List<Sound> decodedBuffers = emitterObject.decodedBuffers;
|
||||||
|
if (decodedBuffers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final AudioPanner panner = audioContext.createPanner();
|
final AudioPanner panner = audioContext.createPanner();
|
||||||
final AudioBufferSource source = audioContext.createBufferSource();
|
final AudioBufferSource source = audioContext.createBufferSource();
|
||||||
final Vector3 location = node.worldLocation;
|
final Vector3 location = node.worldLocation;
|
||||||
|
@ -42,10 +42,11 @@ public class MdxComplexInstance extends ModelInstance {
|
|||||||
public MdxNode[] nodes;
|
public MdxNode[] nodes;
|
||||||
public SkeletalNode[] sortedNodes;
|
public SkeletalNode[] sortedNodes;
|
||||||
public int frame = 0;
|
public int frame = 0;
|
||||||
|
public float floatingFrame = 0;
|
||||||
// Global sequences
|
// Global sequences
|
||||||
public int counter = 0;
|
public int counter = 0;
|
||||||
public int sequence = -1;
|
public int sequence = -1;
|
||||||
public int sequenceLoopMode = 0;
|
public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP;
|
||||||
public boolean sequenceEnded = false;
|
public boolean sequenceEnded = false;
|
||||||
public float[] vertexColor = { 1, 1, 1, 1 };
|
public float[] vertexColor = { 1, 1, 1, 1 };
|
||||||
// Particles do not spawn when the sequence is -1, or when the sequence finished
|
// Particles do not spawn when the sequence is -1, or when the sequence finished
|
||||||
@ -523,34 +524,40 @@ public class MdxComplexInstance extends ModelInstance {
|
|||||||
if (sequenceId != -1) {
|
if (sequenceId != -1) {
|
||||||
final Sequence sequence = model.sequences.get(sequenceId);
|
final Sequence sequence = model.sequences.get(sequenceId);
|
||||||
final long[] interval = sequence.getInterval();
|
final long[] interval = sequence.getInterval();
|
||||||
final int frameTime = (int) (dt * 1000 * this.animationSpeed);
|
final float frameTime = (dt * 1000 * this.animationSpeed);
|
||||||
|
|
||||||
this.frame += frameTime;
|
final int lastIntegerFrame = this.frame;
|
||||||
this.counter += frameTime;
|
this.floatingFrame += frameTime;
|
||||||
|
this.frame = (int) this.floatingFrame;
|
||||||
|
final int integerFrameTime = this.frame - lastIntegerFrame;
|
||||||
|
this.counter += integerFrameTime;
|
||||||
this.allowParticleSpawn = true;
|
this.allowParticleSpawn = true;
|
||||||
|
|
||||||
if (this.frame >= interval[1]) {
|
if (this.floatingFrame >= interval[1]) {
|
||||||
if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 1) && (sequence.getFlags() == 0))) {
|
if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP)
|
||||||
this.frame = (int) interval[0]; // TODO not cast
|
|| ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) {
|
||||||
|
this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast
|
||||||
|
|
||||||
this.resetEventEmitters();
|
this.resetEventEmitters();
|
||||||
}
|
}
|
||||||
else if (this.sequenceLoopMode == 4) { // faux queued animation mode
|
else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation
|
||||||
final int framesPast = this.frame - (int) interval[1];
|
// mode
|
||||||
|
final float framesPast = this.floatingFrame - interval[1];
|
||||||
|
|
||||||
final List<Sequence> sequences = model.sequences;
|
final List<Sequence> sequences = model.sequences;
|
||||||
this.sequence = (this.sequence + 1) % sequences.size();
|
this.sequence = (this.sequence + 1) % sequences.size();
|
||||||
this.frame = (int) sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast
|
this.floatingFrame = sequences.get(this.sequence).getInterval()[0] + framesPast; // TODO not cast
|
||||||
|
this.frame = (int) this.floatingFrame;
|
||||||
this.sequenceEnded = false;
|
this.sequenceEnded = false;
|
||||||
this.resetEventEmitters();
|
this.resetEventEmitters();
|
||||||
this.forced = true;
|
this.forced = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.frame = (int) interval[1]; // TODO not cast
|
this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast
|
||||||
this.counter -= frameTime;
|
this.counter -= integerFrameTime;
|
||||||
this.allowParticleSpawn = false;
|
this.allowParticleSpawn = false;
|
||||||
}
|
}
|
||||||
if (this.sequenceLoopMode == 3) {
|
if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) {
|
||||||
hide();
|
hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,10 +643,12 @@ public class MdxComplexInstance extends ModelInstance {
|
|||||||
if ((id < 0) || (id > (sequences.size() - 1))) {
|
if ((id < 0) || (id > (sequences.size() - 1))) {
|
||||||
this.sequence = -1;
|
this.sequence = -1;
|
||||||
this.frame = 0;
|
this.frame = 0;
|
||||||
|
this.floatingFrame = 0;
|
||||||
this.allowParticleSpawn = false;
|
this.allowParticleSpawn = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast
|
this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast
|
||||||
|
this.floatingFrame = this.frame;
|
||||||
this.sequenceEnded = false;
|
this.sequenceEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -656,7 +665,7 @@ public class MdxComplexInstance extends ModelInstance {
|
|||||||
* and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay
|
* and 2 to always loop. 3 was added by Retera as "hide after done" for gameplay
|
||||||
* spawned effects
|
* spawned effects
|
||||||
*/
|
*/
|
||||||
public MdxComplexInstance setSequenceLoopMode(final int mode) {
|
public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) {
|
||||||
this.sequenceLoopMode = mode;
|
this.sequenceLoopMode = mode;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -109,11 +109,11 @@ public final class SdSequence<TYPE> {
|
|||||||
// with the default value.
|
// with the default value.
|
||||||
if (framesBuilder.get(framesBuilder.size() - 1) != end) {
|
if (framesBuilder.get(framesBuilder.size() - 1) != end) {
|
||||||
framesBuilder.add(end);
|
framesBuilder.add(end);
|
||||||
valuesBuilder.add(valuesBuilder.get(0));
|
valuesBuilder.add(valuesBuilder.get(valuesBuilder.size() - 1));
|
||||||
|
|
||||||
if (interpolationType > 1) {
|
if (interpolationType > 1) {
|
||||||
inTansBuilder.add(inTansBuilder.get(0));
|
inTansBuilder.add(inTansBuilder.get(inTansBuilder.size() - 1));
|
||||||
outTansBuilder.add(outTansBuilder.get(0));
|
outTansBuilder.add(outTansBuilder.get(outTansBuilder.size() - 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.mdx;
|
||||||
|
|
||||||
|
public enum SequenceLoopMode {
|
||||||
|
NEVER_LOOP,
|
||||||
|
MODEL_LOOP,
|
||||||
|
ALWAYS_LOOP,
|
||||||
|
NEVER_LOOP_AND_HIDE_WHEN_DONE, // used by spawned effects
|
||||||
|
LOOP_TO_NEXT_ANIMATION; // used by the Arthas vs Illidan tech demo
|
||||||
|
}
|
@ -24,7 +24,7 @@ public class TgaFile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a TGA image from a file
|
* Read a TGA image from a file
|
||||||
*
|
*
|
||||||
* @param file
|
* @param file
|
||||||
* @return
|
* @return
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
@ -37,7 +37,7 @@ public class TgaFile {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a TGA image from an input stream.
|
* Read a TGA image from an input stream.
|
||||||
*
|
*
|
||||||
* @param name
|
* @param name
|
||||||
* @param stream
|
* @param stream
|
||||||
* @return
|
* @return
|
||||||
@ -142,7 +142,7 @@ public class TgaFile {
|
|||||||
/**
|
/**
|
||||||
* Write a BufferedImage to a TGA file BufferedImages should be TYPE_INT_ARGB or
|
* Write a BufferedImage to a TGA file BufferedImages should be TYPE_INT_ARGB or
|
||||||
* TYPE_INT_RGB
|
* TYPE_INT_RGB
|
||||||
*
|
*
|
||||||
* @param src
|
* @param src
|
||||||
* @param file
|
* @param file
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
|
@ -19,6 +19,7 @@ public class AnimationTokens {
|
|||||||
public static enum SecondaryTag {
|
public static enum SecondaryTag {
|
||||||
ALTERNATE,
|
ALTERNATE,
|
||||||
ALTERNATEEX,
|
ALTERNATEEX,
|
||||||
|
BONE,
|
||||||
CHAIN,
|
CHAIN,
|
||||||
CHANNEL,
|
CHANNEL,
|
||||||
COMPLETE,
|
COMPLETE,
|
||||||
@ -56,6 +57,7 @@ public class AnimationTokens {
|
|||||||
SMALL,
|
SMALL,
|
||||||
SPIKED,
|
SPIKED,
|
||||||
SPIN,
|
SPIN,
|
||||||
|
SPELL,
|
||||||
SWIM,
|
SWIM,
|
||||||
TALK,
|
TALK,
|
||||||
THIRD,
|
THIRD,
|
||||||
|
@ -10,7 +10,11 @@ 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.PrimaryTag;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
||||||
|
|
||||||
public class StandSequence {
|
public class SequenceUtils {
|
||||||
|
public static final EnumSet<SecondaryTag> EMPTY = EnumSet.noneOf(SecondaryTag.class);
|
||||||
|
public static final EnumSet<SecondaryTag> READY = EnumSet.of(SecondaryTag.READY);
|
||||||
|
public static final EnumSet<SecondaryTag> FLESH = EnumSet.of(SecondaryTag.FLESH);
|
||||||
|
public static final EnumSet<SecondaryTag> BONE = EnumSet.of(SecondaryTag.BONE);
|
||||||
|
|
||||||
private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator();
|
private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator();
|
||||||
|
|
||||||
@ -87,7 +91,8 @@ public class StandSequence {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type,
|
public static IndexedSequence selectSequence(final AnimationTokens.PrimaryTag type,
|
||||||
final EnumSet<AnimationTokens.SecondaryTag> tags, final List<Sequence> sequences) {
|
final EnumSet<AnimationTokens.SecondaryTag> tags, final List<Sequence> sequences,
|
||||||
|
final boolean allowRarityVariations) {
|
||||||
final List<IndexedSequence> filtered = filterSequences(type, tags, sequences);
|
final List<IndexedSequence> filtered = filterSequences(type, tags, sequences);
|
||||||
|
|
||||||
filtered.sort(STAND_SEQUENCE_COMPARATOR);
|
filtered.sort(STAND_SEQUENCE_COMPARATOR);
|
||||||
@ -101,7 +106,7 @@ public class StandSequence {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((Math.random() * 10) > rarity) {
|
if (((Math.random() * 10) > rarity) && allowRarityVariations) {
|
||||||
return filtered.get(i);
|
return filtered.get(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,17 +212,22 @@ public class StandSequence {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void randomSequence(final MdxComplexInstance target, final PrimaryTag animationName,
|
public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName,
|
||||||
final EnumSet<SecondaryTag> secondaryAnimationTags) {
|
final EnumSet<SecondaryTag> secondaryAnimationTags, final boolean allowRarityVariations) {
|
||||||
final MdxModel model = (MdxModel) target.model;
|
final MdxModel model = (MdxModel) target.model;
|
||||||
final List<Sequence> sequences = model.getSequences();
|
final List<Sequence> sequences = model.getSequences();
|
||||||
final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences);
|
final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences,
|
||||||
|
allowRarityVariations);
|
||||||
|
|
||||||
if (sequence != null) {
|
if (sequence != null) {
|
||||||
target.setSequence(sequence.index);
|
target.setSequence(sequence.index);
|
||||||
|
return sequence.sequence;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
randomStandSequence(target);
|
if (!secondaryAnimationTags.isEmpty()) {
|
||||||
|
return randomSequence(target, animationName, EMPTY, allowRarityVariations);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,8 @@ public class SplatModel {
|
|||||||
private final Texture texture;
|
private final Texture texture;
|
||||||
private final List<Batch> batches;
|
private final List<Batch> batches;
|
||||||
public final float[] color;
|
public final float[] color;
|
||||||
|
private final List<float[]> locations;
|
||||||
|
private final List<SplatMover> splatInstances;
|
||||||
|
|
||||||
public SplatModel(final GL30 gl, final Texture texture, final List<float[]> locations, final float[] centerOffset,
|
public SplatModel(final GL30 gl, final Texture texture, final List<float[]> locations, final float[] centerOffset,
|
||||||
final List<Consumer<SplatMover>> unitMapping) {
|
final List<Consumer<SplatMover>> unitMapping) {
|
||||||
@ -31,16 +33,49 @@ public class SplatModel {
|
|||||||
this.batches = new ArrayList<>();
|
this.batches = new ArrayList<>();
|
||||||
this.color = new float[] { 1, 1, 1, 1 };
|
this.color = new float[] { 1, 1, 1, 1 };
|
||||||
|
|
||||||
|
this.locations = locations;
|
||||||
|
if ((unitMapping != null) && (unitMapping.size() > 0)) {
|
||||||
|
this.splatInstances = new ArrayList<>();
|
||||||
|
for (int i = 0; i < unitMapping.size(); i++) {
|
||||||
|
this.splatInstances.add(new SplatMover(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.splatInstances = null;
|
||||||
|
}
|
||||||
|
loadBatches(gl, centerOffset);
|
||||||
|
if ((unitMapping != null) && (unitMapping.size() > 0)) {
|
||||||
|
if (this.splatInstances.size() != unitMapping.size()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < this.splatInstances.size(); i++) {
|
||||||
|
unitMapping.get(i).accept(this.splatInstances.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void compact(final GL30 gl, final float[] centerOffset) {
|
||||||
|
// delete all the batches
|
||||||
|
for (final Batch b : this.batches) {
|
||||||
|
// Vertices
|
||||||
|
gl.glDeleteBuffer(b.vertexBuffer);
|
||||||
|
|
||||||
|
// Faces.
|
||||||
|
gl.glDeleteBuffer(b.faceBuffer);
|
||||||
|
}
|
||||||
|
this.batches.clear();
|
||||||
|
|
||||||
|
loadBatches(gl, centerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadBatches(final GL30 gl, final float[] centerOffset) {
|
||||||
final List<float[]> vertices = new ArrayList<>();
|
final List<float[]> vertices = new ArrayList<>();
|
||||||
final List<float[]> uvs = new ArrayList<>();
|
final List<float[]> uvs = new ArrayList<>();
|
||||||
final List<int[]> indices = new ArrayList<>();
|
final List<int[]> indices = new ArrayList<>();
|
||||||
final List<SplatMover> batchRenderUnits = new ArrayList<>();
|
final List<SplatMover> batchRenderUnits = new ArrayList<>();
|
||||||
final int instances = locations.size();
|
final int instances = this.locations.size();
|
||||||
for (int idx = 0; idx < instances; ++idx) {
|
for (int idx = 0; idx < instances; ++idx) {
|
||||||
final Consumer<SplatMover> unit = ((unitMapping != null) && (idx < unitMapping.size()))
|
final float[] locs = this.locations.get(idx);
|
||||||
? unitMapping.get(idx)
|
|
||||||
: null;
|
|
||||||
final float[] locs = locations.get(idx);
|
|
||||||
final float x0 = locs[0];
|
final float x0 = locs[0];
|
||||||
final float y0 = locs[1];
|
final float y0 = locs[1];
|
||||||
final float x1 = locs[2];
|
final float x1 = locs[2];
|
||||||
@ -61,7 +96,8 @@ public class SplatModel {
|
|||||||
* ((int) Math.ceil((x1 - x0) / 128.0) + 1);
|
* ((int) Math.ceil((x1 - x0) / 128.0) + 1);
|
||||||
|
|
||||||
int start = vertices.size();
|
int start = vertices.size();
|
||||||
final SplatMover splatMover = unit == null ? null : new SplatMover(start * 3 * 4, indices.size() * 6 * 2);
|
final SplatMover splatMover = (this.splatInstances == null) ? null
|
||||||
|
: this.splatInstances.get(idx).reset(start * 3 * 4, indices.size() * 6 * 2, idx);
|
||||||
|
|
||||||
final int numVertsToCrate = splatMover == null ? newVerts : maxPossibleVerts;
|
final int numVertsToCrate = splatMover == null ? newVerts : maxPossibleVerts;
|
||||||
if (numVertsToCrate > MAX_VERTICES) {
|
if (numVertsToCrate > MAX_VERTICES) {
|
||||||
@ -124,8 +160,7 @@ public class SplatModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (unit != null) {
|
if (this.splatInstances != null) {
|
||||||
unit.accept(splatMover);
|
|
||||||
batchRenderUnits.add(splatMover);
|
batchRenderUnits.add(splatMover);
|
||||||
|
|
||||||
while (splatMover.indices.size() < maxPossibleFaces) {
|
while (splatMover.indices.size() < maxPossibleFaces) {
|
||||||
@ -140,7 +175,6 @@ public class SplatModel {
|
|||||||
if (indices.size() > 0) {
|
if (indices.size() > 0) {
|
||||||
this.addBatch(gl, vertices, uvs, indices, batchRenderUnits);
|
this.addBatch(gl, vertices, uvs, indices, batchRenderUnits);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBatch(final GL30 gl, final List<float[]> vertices, final List<float[]> uvs,
|
private void addBatch(final GL30 gl, final List<float[]> vertices, final List<float[]> uvs,
|
||||||
@ -213,17 +247,28 @@ public class SplatModel {
|
|||||||
public float uvYScale;
|
public float uvYScale;
|
||||||
public float uvXScale;
|
public float uvXScale;
|
||||||
private int vertexBuffer;
|
private int vertexBuffer;
|
||||||
private final int startOffset;
|
private int startOffset;
|
||||||
private final int start;
|
private int start;
|
||||||
private final List<float[]> vertices = new ArrayList<>();
|
private final List<float[]> vertices = new ArrayList<>();
|
||||||
private final List<float[]> uvs = new ArrayList<>();
|
private final List<float[]> uvs = new ArrayList<>();
|
||||||
private final List<int[]> indices = new ArrayList<>();
|
private final List<int[]> indices = new ArrayList<>();
|
||||||
private final int indicesStartOffset;
|
private int indicesStartOffset;
|
||||||
|
private int index;
|
||||||
|
private final SplatModel splatModel;
|
||||||
|
|
||||||
private SplatMover(final int i, final int indicesStartOffset) {
|
private SplatMover(final SplatModel splatModel) {
|
||||||
|
this.splatModel = splatModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SplatMover reset(final int i, final int indicesStartOffset, final int index) {
|
||||||
this.startOffset = i;
|
this.startOffset = i;
|
||||||
this.indicesStartOffset = indicesStartOffset;
|
this.indicesStartOffset = indicesStartOffset;
|
||||||
this.start = i / 12;
|
this.start = i / 12;
|
||||||
|
this.index = index;
|
||||||
|
this.vertices.clear();
|
||||||
|
this.uvs.clear();
|
||||||
|
this.indices.clear();
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void move(final float deltaX, final float deltaY, final float[] centerOffset) {
|
public void move(final float deltaX, final float deltaY, final float[] centerOffset) {
|
||||||
@ -325,5 +370,40 @@ public class SplatModel {
|
|||||||
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2),
|
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2),
|
||||||
4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs));
|
4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void destroy(final GL30 gl, final float[] centerOffset) {
|
||||||
|
this.splatModel.locations.remove(this.index);
|
||||||
|
this.splatModel.splatInstances.remove(this.index);
|
||||||
|
this.splatModel.compact(gl, centerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hide() {
|
||||||
|
// does not remove the shadow, just makes it not show, so it would still be
|
||||||
|
// using GPU resources
|
||||||
|
final GL30 gl = Gdx.gl30;
|
||||||
|
for (final float[] vertex : this.vertices) {
|
||||||
|
for (int i = 0; i < vertex.length; i++) {
|
||||||
|
vertex[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final int[] indices : this.indices) {
|
||||||
|
for (int i = 0; i < indices.length; i++) {
|
||||||
|
indices[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (final float[] uv : this.uvs) {
|
||||||
|
for (int i = 0; i < uv.length; i++) {
|
||||||
|
uv[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gl.glBindBuffer(GL30.GL_ARRAY_BUFFER, this.vertexBuffer);
|
||||||
|
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.startOffset, 4 * 3 * this.vertices.size(),
|
||||||
|
RenderMathUtils.wrap(this.vertices));
|
||||||
|
gl.glBindBuffer(GL30.GL_ELEMENT_ARRAY_BUFFER, this.faceBuffer);
|
||||||
|
gl.glBufferSubData(GL30.GL_ELEMENT_ARRAY_BUFFER, this.indicesStartOffset, 6 * 2 * this.indices.size(),
|
||||||
|
RenderMathUtils.wrapFaces(this.indices));
|
||||||
|
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2),
|
||||||
|
4 * 2 * this.uvs.size(), RenderMathUtils.wrap(this.uvs));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,109 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.audio.Sound;
|
||||||
|
import com.badlogic.gdx.utils.TimeUtils;
|
||||||
|
import com.etheller.warsmash.datasources.DataSource;
|
||||||
|
import com.etheller.warsmash.units.DataTable;
|
||||||
|
import com.etheller.warsmash.units.Element;
|
||||||
|
import com.etheller.warsmash.util.DataSourceFileHandle;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioBufferSource;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioContext;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioPanner;
|
||||||
|
import com.etheller.warsmash.viewer5.gl.Extensions;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
||||||
|
|
||||||
|
public final class UnitAckSound {
|
||||||
|
private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
private final List<Sound> sounds = new ArrayList<>();
|
||||||
|
private final float volume;
|
||||||
|
private final float pitch;
|
||||||
|
private final float pitchVariation;
|
||||||
|
private final float minDistance;
|
||||||
|
private final float maxDistance;
|
||||||
|
private final float distanceCutoff;
|
||||||
|
|
||||||
|
private Sound lastPlayedSound;
|
||||||
|
|
||||||
|
public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName,
|
||||||
|
final String soundType) {
|
||||||
|
final Element row = unitAckSounds.get(soundName + soundType);
|
||||||
|
if (row == null) {
|
||||||
|
return SILENT;
|
||||||
|
}
|
||||||
|
final String fileNames = row.getField("FileNames");
|
||||||
|
String directoryBase = row.getField("DirectoryBase");
|
||||||
|
if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) {
|
||||||
|
directoryBase += "\\";
|
||||||
|
}
|
||||||
|
final float volume = row.getFieldFloatValue("Volume");
|
||||||
|
final float pitch = row.getFieldFloatValue("Pitch");
|
||||||
|
final float pitchVariation = row.getFieldFloatValue("PitchVariance");
|
||||||
|
final float minDistance = row.getFieldFloatValue("MinDistance");
|
||||||
|
final float maxDistance = row.getFieldFloatValue("MaxDistance");
|
||||||
|
final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff");
|
||||||
|
final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff);
|
||||||
|
for (final String fileName : fileNames.split(",")) {
|
||||||
|
String filePath = directoryBase + fileName;
|
||||||
|
if (!filePath.toLowerCase().endsWith(".wav")) {
|
||||||
|
filePath += ".wav";
|
||||||
|
}
|
||||||
|
if (dataSource.has(filePath)) {
|
||||||
|
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance,
|
||||||
|
final float maxDistance, final float distanceCutoff) {
|
||||||
|
this.volume = volume;
|
||||||
|
this.pitch = pitch;
|
||||||
|
this.pitchVariation = pitchVariation;
|
||||||
|
this.minDistance = minDistance;
|
||||||
|
this.maxDistance = maxDistance;
|
||||||
|
this.distanceCutoff = distanceCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean play(final AudioContext audioContext, final RenderUnit unit) {
|
||||||
|
return play(audioContext, unit, (int) (Math.random() * this.sounds.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean play(final AudioContext audioContext, final RenderUnit unit, final int index) {
|
||||||
|
if (this.sounds.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final long millisTime = TimeUtils.millis();
|
||||||
|
if (millisTime < unit.lastUnitResponseEndTimeMillis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AudioPanner panner = audioContext.createPanner();
|
||||||
|
final AudioBufferSource source = audioContext.createBufferSource();
|
||||||
|
|
||||||
|
// Panner settings
|
||||||
|
panner.setPosition(unit.location[0], unit.location[1], unit.location[2]);
|
||||||
|
panner.maxDistance = this.distanceCutoff;
|
||||||
|
panner.refDistance = this.minDistance;
|
||||||
|
panner.connect(audioContext.destination);
|
||||||
|
|
||||||
|
// Source.
|
||||||
|
source.buffer = this.sounds.get(index);
|
||||||
|
source.connect(panner);
|
||||||
|
|
||||||
|
// Make a sound.
|
||||||
|
source.start(0);
|
||||||
|
this.lastPlayedSound = source.buffer;
|
||||||
|
final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound);
|
||||||
|
unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSoundCount() {
|
||||||
|
return this.sounds.size();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.audio.Sound;
|
||||||
|
import com.badlogic.gdx.utils.TimeUtils;
|
||||||
|
import com.etheller.warsmash.datasources.DataSource;
|
||||||
|
import com.etheller.warsmash.units.DataTable;
|
||||||
|
import com.etheller.warsmash.units.Element;
|
||||||
|
import com.etheller.warsmash.util.DataSourceFileHandle;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioBufferSource;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioContext;
|
||||||
|
import com.etheller.warsmash.viewer5.AudioPanner;
|
||||||
|
import com.etheller.warsmash.viewer5.gl.Extensions;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
||||||
|
|
||||||
|
public final class UnitSound {
|
||||||
|
private static final UnitSound SILENT = new UnitSound(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
private final List<Sound> sounds = new ArrayList<>();
|
||||||
|
private final float volume;
|
||||||
|
private final float pitch;
|
||||||
|
private final float pitchVariation;
|
||||||
|
private final float minDistance;
|
||||||
|
private final float maxDistance;
|
||||||
|
private final float distanceCutoff;
|
||||||
|
|
||||||
|
private Sound lastPlayedSound;
|
||||||
|
|
||||||
|
public static UnitSound create(final DataSource dataSource, final DataTable unitAckSounds, final String soundName,
|
||||||
|
final String soundType) {
|
||||||
|
final Element row = unitAckSounds.get(soundName + soundType);
|
||||||
|
if (row == null) {
|
||||||
|
return SILENT;
|
||||||
|
}
|
||||||
|
final String fileNames = row.getField("FileNames");
|
||||||
|
String directoryBase = row.getField("DirectoryBase");
|
||||||
|
if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) {
|
||||||
|
directoryBase += "\\";
|
||||||
|
}
|
||||||
|
final float volume = row.getFieldFloatValue("Volume");
|
||||||
|
final float pitch = row.getFieldFloatValue("Pitch");
|
||||||
|
final float pitchVariation = row.getFieldFloatValue("PitchVariance");
|
||||||
|
final float minDistance = row.getFieldFloatValue("MinDistance");
|
||||||
|
final float maxDistance = row.getFieldFloatValue("MaxDistance");
|
||||||
|
final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff");
|
||||||
|
final UnitSound sound = new UnitSound(volume, pitch, pitchVariation, minDistance, maxDistance, distanceCutoff);
|
||||||
|
for (final String fileName : fileNames.split(",")) {
|
||||||
|
String filePath = directoryBase + fileName;
|
||||||
|
if (!filePath.toLowerCase().endsWith(".wav")) {
|
||||||
|
filePath += ".wav";
|
||||||
|
}
|
||||||
|
if (dataSource.has(filePath)) {
|
||||||
|
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnitSound(final float volume, final float pitch, final float pitchVariation, final float minDistance,
|
||||||
|
final float maxDistance, final float distanceCutoff) {
|
||||||
|
this.volume = volume;
|
||||||
|
this.pitch = pitch;
|
||||||
|
this.pitchVariation = pitchVariation;
|
||||||
|
this.minDistance = minDistance;
|
||||||
|
this.maxDistance = maxDistance;
|
||||||
|
this.distanceCutoff = distanceCutoff;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean playUnitResponse(final AudioContext audioContext, final RenderUnit unit) {
|
||||||
|
return playUnitResponse(audioContext, unit, (int) (Math.random() * this.sounds.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean playUnitResponse(final AudioContext audioContext, final RenderUnit unit, final int index) {
|
||||||
|
final long millisTime = TimeUtils.millis();
|
||||||
|
if (millisTime < unit.lastUnitResponseEndTimeMillis) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (play(audioContext, unit.location[0], unit.location[1])) {
|
||||||
|
final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound);
|
||||||
|
unit.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean play(final AudioContext audioContext, final float x, final float y) {
|
||||||
|
return play(audioContext, x, y, (int) (Math.random() * this.sounds.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean play(final AudioContext audioContext, final float x, final float y, final int index) {
|
||||||
|
if (this.sounds.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AudioPanner panner = audioContext.createPanner();
|
||||||
|
final AudioBufferSource source = audioContext.createBufferSource();
|
||||||
|
|
||||||
|
// Panner settings
|
||||||
|
panner.setPosition(x, y, 0);
|
||||||
|
panner.maxDistance = this.distanceCutoff;
|
||||||
|
panner.refDistance = this.minDistance;
|
||||||
|
panner.connect(audioContext.destination);
|
||||||
|
|
||||||
|
// Source.
|
||||||
|
source.buffer = this.sounds.get(index);
|
||||||
|
source.connect(panner);
|
||||||
|
|
||||||
|
// Make a sound.
|
||||||
|
source.start(0);
|
||||||
|
this.lastPlayedSound = source.buffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSoundCount() {
|
||||||
|
return this.sounds.size();
|
||||||
|
}
|
||||||
|
}
|
@ -1,127 +1,23 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x;
|
package com.etheller.warsmash.viewer5.handlers.w3x;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
|
||||||
import com.badlogic.gdx.audio.Sound;
|
|
||||||
import com.badlogic.gdx.utils.TimeUtils;
|
|
||||||
import com.etheller.warsmash.datasources.DataSource;
|
import com.etheller.warsmash.datasources.DataSource;
|
||||||
import com.etheller.warsmash.units.DataTable;
|
import com.etheller.warsmash.units.DataTable;
|
||||||
import com.etheller.warsmash.units.Element;
|
|
||||||
import com.etheller.warsmash.util.DataSourceFileHandle;
|
|
||||||
import com.etheller.warsmash.viewer5.AudioBufferSource;
|
|
||||||
import com.etheller.warsmash.viewer5.AudioContext;
|
|
||||||
import com.etheller.warsmash.viewer5.AudioPanner;
|
|
||||||
import com.etheller.warsmash.viewer5.gl.Extensions;
|
|
||||||
|
|
||||||
public class UnitSoundset {
|
public class UnitSoundset {
|
||||||
public final UnitAckSound what;
|
public final UnitSound what;
|
||||||
public final UnitAckSound pissed;
|
public final UnitSound pissed;
|
||||||
public final UnitAckSound yesAttack;
|
public final UnitSound yesAttack;
|
||||||
public final UnitAckSound yes;
|
public final UnitSound yes;
|
||||||
public final UnitAckSound ready;
|
public final UnitSound ready;
|
||||||
public final UnitAckSound warcry;
|
public final UnitSound warcry;
|
||||||
|
|
||||||
public UnitSoundset(final DataSource dataSource, final DataTable unitAckSounds, final String soundName) {
|
public UnitSoundset(final DataSource dataSource, final DataTable unitAckSounds, final String soundName) {
|
||||||
this.what = UnitAckSound.create(dataSource, unitAckSounds, soundName, "What");
|
this.what = UnitSound.create(dataSource, unitAckSounds, soundName, "What");
|
||||||
this.pissed = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Pissed");
|
this.pissed = UnitSound.create(dataSource, unitAckSounds, soundName, "Pissed");
|
||||||
this.yesAttack = UnitAckSound.create(dataSource, unitAckSounds, soundName, "YesAttack");
|
this.yesAttack = UnitSound.create(dataSource, unitAckSounds, soundName, "YesAttack");
|
||||||
this.yes = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Yes");
|
this.yes = UnitSound.create(dataSource, unitAckSounds, soundName, "Yes");
|
||||||
this.ready = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Ready");
|
this.ready = UnitSound.create(dataSource, unitAckSounds, soundName, "Ready");
|
||||||
this.warcry = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Warcry");
|
this.warcry = UnitSound.create(dataSource, unitAckSounds, soundName, "Warcry");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class UnitAckSound {
|
|
||||||
private static final UnitAckSound SILENT = new UnitAckSound(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
private final List<Sound> sounds = new ArrayList<>();
|
|
||||||
private final float volume;
|
|
||||||
private final float pitch;
|
|
||||||
private final float pitchVariation;
|
|
||||||
private final float minDistance;
|
|
||||||
private final float maxDistance;
|
|
||||||
private final float distanceCutoff;
|
|
||||||
|
|
||||||
private Sound lastPlayedSound;
|
|
||||||
|
|
||||||
public static UnitAckSound create(final DataSource dataSource, final DataTable unitAckSounds,
|
|
||||||
final String soundName, final String soundType) {
|
|
||||||
final Element row = unitAckSounds.get(soundName + soundType);
|
|
||||||
if (row == null) {
|
|
||||||
return SILENT;
|
|
||||||
}
|
|
||||||
final String fileNames = row.getField("FileNames");
|
|
||||||
String directoryBase = row.getField("DirectoryBase");
|
|
||||||
if ((directoryBase.length() > 1) && !directoryBase.endsWith("\\")) {
|
|
||||||
directoryBase += "\\";
|
|
||||||
}
|
|
||||||
final float volume = row.getFieldFloatValue("Volume");
|
|
||||||
final float pitch = row.getFieldFloatValue("Pitch");
|
|
||||||
final float pitchVariation = row.getFieldFloatValue("PitchVariance");
|
|
||||||
final float minDistance = row.getFieldFloatValue("MinDistance");
|
|
||||||
final float maxDistance = row.getFieldFloatValue("MaxDistance");
|
|
||||||
final float distanceCutoff = row.getFieldFloatValue("DistanceCutoff");
|
|
||||||
final UnitAckSound sound = new UnitAckSound(volume, pitch, pitchVariation, minDistance, maxDistance,
|
|
||||||
distanceCutoff);
|
|
||||||
for (final String fileName : fileNames.split(",")) {
|
|
||||||
String filePath = directoryBase + fileName;
|
|
||||||
if (!filePath.toLowerCase().endsWith(".wav")) {
|
|
||||||
filePath += ".wav";
|
|
||||||
}
|
|
||||||
if (dataSource.has(filePath)) {
|
|
||||||
sound.sounds.add(Gdx.audio.newSound(new DataSourceFileHandle(dataSource, filePath)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnitAckSound(final float volume, final float pitch, final float pitchVariation, final float minDistance,
|
|
||||||
final float maxDistance, final float distanceCutoff) {
|
|
||||||
this.volume = volume;
|
|
||||||
this.pitch = pitch;
|
|
||||||
this.pitchVariation = pitchVariation;
|
|
||||||
this.minDistance = minDistance;
|
|
||||||
this.maxDistance = maxDistance;
|
|
||||||
this.distanceCutoff = distanceCutoff;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean play(final AudioContext audioContext, final float x, final float y) {
|
|
||||||
return play(audioContext, x, y, (int) (Math.random() * this.sounds.size()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean play(final AudioContext audioContext, final float x, final float y, final int index) {
|
|
||||||
if (this.sounds.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final long millisTime = TimeUtils.millis();
|
|
||||||
if (millisTime < audioContext.lastUnitResponseEndTimeMillis) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final AudioPanner panner = audioContext.createPanner();
|
|
||||||
final AudioBufferSource source = audioContext.createBufferSource();
|
|
||||||
|
|
||||||
// Panner settings
|
|
||||||
panner.setPosition(x, y, 0);
|
|
||||||
panner.maxDistance = this.distanceCutoff;
|
|
||||||
panner.refDistance = this.minDistance;
|
|
||||||
panner.connect(audioContext.destination);
|
|
||||||
|
|
||||||
// Source.
|
|
||||||
source.buffer = this.sounds.get(index);
|
|
||||||
source.connect(panner);
|
|
||||||
|
|
||||||
// Make a sound.
|
|
||||||
source.start(0);
|
|
||||||
this.lastPlayedSound = source.buffer;
|
|
||||||
final float duration = Extensions.soundLengthExtension.getDuration(this.lastPlayedSound);
|
|
||||||
audioContext.lastUnitResponseEndTimeMillis = millisTime + (long) (1000 * duration);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSoundCount() {
|
|
||||||
return this.sounds.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ import java.util.Map;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.apache.commons.compress.utils.IOUtils;
|
import org.apache.commons.compress.utils.IOUtils;
|
||||||
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
|
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
|
||||||
|
|
||||||
@ -60,11 +62,14 @@ import com.etheller.warsmash.viewer5.gl.WebGL;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxHandler;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
|
||||||
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
|
import com.etheller.warsmash.viewer5.handlers.tga.TgaFile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
|
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;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
|
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile;
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackProjectile;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderEffect;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem;
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderItem;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
@ -74,11 +79,13 @@ 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.CAbility;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack;
|
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.abilities.CAbilityMove;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver;
|
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.CWidgetAbilityTargetCheckReceiver;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController;
|
||||||
|
|
||||||
import mpq.MPQArchive;
|
import mpq.MPQArchive;
|
||||||
import mpq.MPQException;
|
import mpq.MPQException;
|
||||||
@ -129,7 +136,7 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
public MappedData unitMetaData = new MappedData();
|
public MappedData unitMetaData = new MappedData();
|
||||||
public List<RenderUnit> units = new ArrayList<>();
|
public List<RenderUnit> units = new ArrayList<>();
|
||||||
public List<RenderItem> items = new ArrayList<>();
|
public List<RenderItem> items = new ArrayList<>();
|
||||||
public List<RenderAttackProjectile> projectiles = new ArrayList<>();
|
public List<RenderEffect> projectiles = new ArrayList<>();
|
||||||
public boolean unitsReady;
|
public boolean unitsReady;
|
||||||
public War3Map mapMpq;
|
public War3Map mapMpq;
|
||||||
public PathSolver mapPathSolver = PathSolver.DEFAULT;
|
public PathSolver mapPathSolver = PathSolver.DEFAULT;
|
||||||
@ -143,6 +150,7 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
public List<SplatModel> selModels = new ArrayList<>();
|
public List<SplatModel> selModels = new ArrayList<>();
|
||||||
public List<RenderUnit> selected = new ArrayList<>();
|
public List<RenderUnit> selected = new ArrayList<>();
|
||||||
private DataTable unitAckSoundsTable;
|
private DataTable unitAckSoundsTable;
|
||||||
|
private DataTable unitCombatSoundsTable;
|
||||||
private DataTable miscData;
|
private DataTable miscData;
|
||||||
private DataTable unitGlobalStrings;
|
private DataTable unitGlobalStrings;
|
||||||
private MdxComplexInstance confirmationInstance;
|
private MdxComplexInstance confirmationInstance;
|
||||||
@ -159,6 +167,8 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
|
|
||||||
private final List<SelectionCircleSize> selectionCircleSizes = new ArrayList<>();
|
private final List<SelectionCircleSize> selectionCircleSizes = new ArrayList<>();
|
||||||
|
|
||||||
|
private final Map<CUnit, RenderUnit> unitToRenderPeer = new HashMap<>();
|
||||||
|
|
||||||
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
|
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
|
||||||
super(dataSource, canvas);
|
super(dataSource, canvas);
|
||||||
this.gameDataSource = dataSource;
|
this.gameDataSource = dataSource;
|
||||||
@ -229,6 +239,11 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) {
|
try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) {
|
||||||
this.unitAckSoundsTable.readSLK(terrainSlkStream);
|
this.unitAckSoundsTable.readSLK(terrainSlkStream);
|
||||||
}
|
}
|
||||||
|
this.unitCombatSoundsTable = new DataTable(worldEditStrings);
|
||||||
|
try (InputStream terrainSlkStream = this.dataSource
|
||||||
|
.getResourceAsStream("UI\\SoundInfo\\UnitCombatSounds.slk")) {
|
||||||
|
this.unitCombatSoundsTable.readSLK(terrainSlkStream);
|
||||||
|
}
|
||||||
this.miscData = new DataTable(worldEditStrings);
|
this.miscData = new DataTable(worldEditStrings);
|
||||||
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) {
|
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) {
|
||||||
this.miscData.readTXT(miscDataTxtStream, true);
|
this.miscData.readTXT(miscDataTxtStream, true);
|
||||||
@ -236,6 +251,9 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) {
|
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) {
|
||||||
this.miscData.readTXT(miscDataTxtStream, true);
|
this.miscData.readTXT(miscDataTxtStream, true);
|
||||||
}
|
}
|
||||||
|
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) {
|
||||||
|
this.miscData.readTXT(miscDataTxtStream, true);
|
||||||
|
}
|
||||||
this.unitGlobalStrings = new DataTable(worldEditStrings);
|
this.unitGlobalStrings = new DataTable(worldEditStrings);
|
||||||
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) {
|
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) {
|
||||||
this.unitGlobalStrings.readTXT(miscDataTxtStream, true);
|
this.unitGlobalStrings.readTXT(miscDataTxtStream, true);
|
||||||
@ -294,7 +312,8 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) {
|
try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) {
|
||||||
if (mapStream == null) {
|
if (mapStream == null) {
|
||||||
tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource,
|
tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource,
|
||||||
new SubdirDataSource(compoundDataSource, tileset + ".mpq/")));
|
new SubdirDataSource(compoundDataSource, tileset + ".mpq/"),
|
||||||
|
new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/")));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final byte[] mapData = IOUtils.toByteArray(mapStream);
|
final byte[] mapData = IOUtils.toByteArray(mapStream);
|
||||||
@ -306,7 +325,8 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
}
|
}
|
||||||
catch (final IOException exc) {
|
catch (final IOException exc) {
|
||||||
tilesetSource = new CompoundDataSource(
|
tilesetSource = new CompoundDataSource(
|
||||||
Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/")));
|
Arrays.asList(compoundDataSource, new SubdirDataSource(compoundDataSource, tileset + ".mpq/"),
|
||||||
|
new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (final MPQException e) {
|
catch (final MPQException e) {
|
||||||
@ -339,7 +359,7 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx",
|
final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx",
|
||||||
PathSolver.DEFAULT, null);
|
PathSolver.DEFAULT, null);
|
||||||
this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance();
|
this.confirmationInstance = (MdxComplexInstance) confirmation.addInstance();
|
||||||
this.confirmationInstance.setSequenceLoopMode(3);
|
this.confirmationInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE);
|
||||||
this.confirmationInstance.setSequence(0);
|
this.confirmationInstance.setSequence(0);
|
||||||
this.confirmationInstance.setScene(this.worldScene);
|
this.confirmationInstance.setScene(this.worldScene);
|
||||||
|
|
||||||
@ -352,57 +372,124 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
|
|
||||||
final Warcraft3MapObjectData modifications = this.mapMpq.readModifications();
|
final Warcraft3MapObjectData modifications = this.mapMpq.readModifications();
|
||||||
this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(),
|
this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(),
|
||||||
new ProjectileCreator() {
|
new SimulationRenderController() {
|
||||||
|
private final Map<String, UnitSound> keyToCombatSound = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CAttackProjectile create(final CSimulation simulation, final CUnit source,
|
public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX,
|
||||||
final int attackIndex, final CWidget target) {
|
final float launchY, final float launchFacing, final CUnit source,
|
||||||
|
final CUnitAttackMissile unitAttack, final CWidget target, final float damage,
|
||||||
|
final int bounceIndex) {
|
||||||
final War3ID typeId = source.getTypeId();
|
final War3ID typeId = source.getTypeId();
|
||||||
final int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(typeId);
|
final int projectileSpeed = unitAttack.getProjectileSpeed();
|
||||||
final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(typeId);
|
final float projectileArc = unitAttack.getProjectileArc();
|
||||||
String a1MissileArt = simulation.getUnitData().getA1MissileArt(typeId);
|
String missileArt = unitAttack.getProjectileArt();
|
||||||
final int a1MinDamage = simulation.getUnitData().getA1MinDamage(typeId);
|
|
||||||
final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(typeId);
|
|
||||||
final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId);
|
final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId);
|
||||||
final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId);
|
final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId);
|
||||||
final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId);
|
final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId);
|
||||||
|
|
||||||
final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage)
|
if (missileArt.toLowerCase().endsWith(".mdl")) {
|
||||||
+ a1MinDamage;
|
missileArt = missileArt.substring(0, missileArt.length() - 4);
|
||||||
if (a1MissileArt.toLowerCase().endsWith(".mdl")) {
|
|
||||||
a1MissileArt = a1MissileArt.substring(0, a1MissileArt.length() - 4);
|
|
||||||
}
|
}
|
||||||
if (!a1MissileArt.toLowerCase().endsWith(".mdx")) {
|
if (!missileArt.toLowerCase().endsWith(".mdx")) {
|
||||||
a1MissileArt += ".mdx";
|
missileArt += ".mdx";
|
||||||
}
|
}
|
||||||
final float facing = (float) Math.toRadians(source.getFacing());
|
final float facing = launchFacing;
|
||||||
final float sinFacing = (float) Math.sin(facing);
|
final float sinFacing = (float) Math.sin(facing);
|
||||||
final float cosFacing = (float) Math.cos(facing);
|
final float cosFacing = (float) Math.cos(facing);
|
||||||
final float x = (source.getX() + (projectileLaunchY * cosFacing))
|
final float x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing);
|
||||||
- (projectileLaunchX * sinFacing);
|
final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing);
|
||||||
final float y = source.getY() + (projectileLaunchY * sinFacing)
|
|
||||||
+ (projectileLaunchX * cosFacing);
|
|
||||||
|
|
||||||
final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight()
|
final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight()
|
||||||
+ projectileLaunchZ;
|
+ projectileLaunchZ;
|
||||||
final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y,
|
final CAttackProjectile simulationAttackProjectile = new CAttackProjectile(x, y,
|
||||||
a1ProjectileSpeed, target, source, damage);
|
projectileSpeed, target, source, damage, unitAttack, bounceIndex);
|
||||||
|
|
||||||
final MdxModel model = (MdxModel) load(a1MissileArt, War3MapViewer.this.mapPathSolver,
|
final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver,
|
||||||
War3MapViewer.this.solverParams);
|
War3MapViewer.this.solverParams);
|
||||||
final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance();
|
final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance();
|
||||||
modelInstance.setTeamColor(source.getPlayerIndex());
|
modelInstance.setTeamColor(source.getPlayerIndex());
|
||||||
modelInstance.setScene(War3MapViewer.this.worldScene);
|
modelInstance.setScene(War3MapViewer.this.worldScene);
|
||||||
StandSequence.randomBirthSequence(modelInstance);
|
if (bounceIndex == 0) {
|
||||||
|
SequenceUtils.randomBirthSequence(modelInstance);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SequenceUtils.randomStandSequence(modelInstance);
|
||||||
|
}
|
||||||
modelInstance.setLocation(x, y, height);
|
modelInstance.setLocation(x, y, height);
|
||||||
final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile(
|
final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile(
|
||||||
simulationAttackProjectile, modelInstance, height, a1ProjectileArc, War3MapViewer.this);
|
simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this);
|
||||||
|
|
||||||
War3MapViewer.this.projectiles.add(renderAttackProjectile);
|
War3MapViewer.this.projectiles.add(renderAttackProjectile);
|
||||||
|
|
||||||
return simulationAttackProjectile;
|
return simulationAttackProjectile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createInstantAttackEffect(final CSimulation cSimulation, final CUnit source,
|
||||||
|
final CUnitAttackInstant unitAttack, final CWidget target) {
|
||||||
|
final War3ID typeId = source.getTypeId();
|
||||||
|
|
||||||
|
String missileArt = unitAttack.getProjectileArt();
|
||||||
|
final float projectileLaunchX = War3MapViewer.this.simulation.getUnitData()
|
||||||
|
.getProjectileLaunchX(typeId);
|
||||||
|
final float projectileLaunchY = War3MapViewer.this.simulation.getUnitData()
|
||||||
|
.getProjectileLaunchY(typeId);
|
||||||
|
if (missileArt.toLowerCase().endsWith(".mdl")) {
|
||||||
|
missileArt = missileArt.substring(0, missileArt.length() - 4);
|
||||||
|
}
|
||||||
|
if (!missileArt.toLowerCase().endsWith(".mdx")) {
|
||||||
|
missileArt += ".mdx";
|
||||||
|
}
|
||||||
|
final float facing = (float) Math.toRadians(source.getFacing());
|
||||||
|
final float sinFacing = (float) Math.sin(facing);
|
||||||
|
final float cosFacing = (float) Math.cos(facing);
|
||||||
|
final float x = (source.getX() + (projectileLaunchY * cosFacing))
|
||||||
|
+ (projectileLaunchX * sinFacing);
|
||||||
|
final float y = (source.getY() + (projectileLaunchY * sinFacing))
|
||||||
|
- (projectileLaunchX * cosFacing);
|
||||||
|
|
||||||
|
final float targetX = target.getX();
|
||||||
|
final float targetY = target.getY();
|
||||||
|
final float angleToTarget = (float) Math.atan2(targetY - y, targetX - x);
|
||||||
|
|
||||||
|
final float height = War3MapViewer.this.terrain.getGroundHeight(targetX, targetY)
|
||||||
|
+ target.getFlyHeight() + target.getImpactZ();
|
||||||
|
|
||||||
|
final MdxModel model = (MdxModel) load(missileArt, War3MapViewer.this.mapPathSolver,
|
||||||
|
War3MapViewer.this.solverParams);
|
||||||
|
final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance();
|
||||||
|
modelInstance.setTeamColor(source.getPlayerIndex());
|
||||||
|
modelInstance.setScene(War3MapViewer.this.worldScene);
|
||||||
|
SequenceUtils.randomBirthSequence(modelInstance);
|
||||||
|
modelInstance.setLocation(targetX, targetY, height);
|
||||||
|
War3MapViewer.this.projectiles
|
||||||
|
.add(new RenderAttackInstant(modelInstance, War3MapViewer.this, angleToTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawnUnitDamageSound(final CUnit damagedUnit, final String weaponSound,
|
||||||
|
final String armorType) {
|
||||||
|
final String key = weaponSound + armorType;
|
||||||
|
UnitSound combatSound = this.keyToCombatSound.get(key);
|
||||||
|
if (combatSound == null) {
|
||||||
|
combatSound = UnitSound.create(War3MapViewer.this.dataSource,
|
||||||
|
War3MapViewer.this.unitCombatSoundsTable, weaponSound, armorType);
|
||||||
|
this.keyToCombatSound.put(key, combatSound);
|
||||||
|
}
|
||||||
|
combatSound.play(War3MapViewer.this.worldScene.audioContext, damagedUnit.getX(),
|
||||||
|
damagedUnit.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeUnit(final CUnit unit) {
|
||||||
|
final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit);
|
||||||
|
War3MapViewer.this.units.remove(renderUnit);
|
||||||
|
War3MapViewer.this.worldScene.removeInstance(renderUnit.instance);
|
||||||
|
}
|
||||||
}, this.terrain.pathingGrid,
|
}, this.terrain.pathingGrid,
|
||||||
new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128));
|
new Rectangle(centerOffset[0], centerOffset[1], (mapSize[0] * 128f) - 128, (mapSize[1] * 128f) - 128),
|
||||||
|
this.seededRandom, w3iFile.getPlayers());
|
||||||
|
|
||||||
if (this.doodadsAndDestructiblesLoaded) {
|
if (this.doodadsAndDestructiblesLoaded) {
|
||||||
this.loadDoodadsAndDestructibles(modifications);
|
this.loadDoodadsAndDestructibles(modifications);
|
||||||
@ -596,6 +683,7 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
MutableGameObject row = null;
|
MutableGameObject row = null;
|
||||||
String path = null;
|
String path = null;
|
||||||
Splat unitShadowSplat = null;
|
Splat unitShadowSplat = null;
|
||||||
|
BufferedImage buildingPathingPixelMap = null;
|
||||||
|
|
||||||
// Hardcoded?
|
// Hardcoded?
|
||||||
WorldEditorDataType type = null;
|
WorldEditorDataType type = null;
|
||||||
@ -693,14 +781,23 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
}
|
}
|
||||||
final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0);
|
final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0);
|
||||||
if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) {
|
if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) {
|
||||||
BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
|
buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
|
||||||
if (bufferedImage == null) {
|
if (buildingPathingPixelMap == null) {
|
||||||
bufferedImage = TgaFile.readTGA(pathingTexture,
|
if (pathingTexture.toLowerCase().endsWith(".tga")) {
|
||||||
this.mapMpq.getResourceAsStream(pathingTexture));
|
buildingPathingPixelMap = TgaFile.readTGA(pathingTexture,
|
||||||
this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage);
|
this.mapMpq.getResourceAsStream(pathingTexture));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try (InputStream stream = this.mapMpq.getResourceAsStream(pathingTexture)) {
|
||||||
|
buildingPathingPixelMap = ImageIO.read(stream);
|
||||||
|
System.out.println("LOADING BLP PATHING: " + pathingTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.filePathToPathingMap.put(pathingTexture.toLowerCase(), buildingPathingPixelMap);
|
||||||
}
|
}
|
||||||
this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0],
|
this.terrain.pathingGrid.blitPathingOverlayTexture(unit.getLocation()[0],
|
||||||
unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()), bufferedImage);
|
unit.getLocation()[1], (int) Math.toDegrees(unit.getAngle()),
|
||||||
|
buildingPathingPixelMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0);
|
final String soundName = row.getFieldAsString(UNIT_SOUNDSET, 0);
|
||||||
@ -724,19 +821,12 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
portraitModel = model;
|
portraitModel = model;
|
||||||
}
|
}
|
||||||
if (type == WorldEditorDataType.UNITS) {
|
if (type == WorldEditorDataType.UNITS) {
|
||||||
float angle;
|
final float angle = (float) Math.toDegrees(unit.getAngle());
|
||||||
if (this.simulation.getUnitData().isBuilding(row.getAlias())) {
|
|
||||||
// TODO pretty sure 270 is a Gameplay Constants value that should be dynamically
|
|
||||||
// loaded
|
|
||||||
angle = 270.0f;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
angle = (float) Math.toDegrees(unit.getAngle());
|
|
||||||
}
|
|
||||||
final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(),
|
final CUnit simulationUnit = this.simulation.createUnit(row.getAlias(), unit.getPlayer(),
|
||||||
unit.getLocation()[0], unit.getLocation()[1], angle);
|
unit.getLocation()[0], unit.getLocation()[1], angle, buildingPathingPixelMap);
|
||||||
final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel,
|
final RenderUnit renderUnit = new RenderUnit(this, model, row, unit, soundset, portraitModel,
|
||||||
simulationUnit);
|
simulationUnit);
|
||||||
|
this.unitToRenderPeer.put(simulationUnit, renderUnit);
|
||||||
this.units.add(renderUnit);
|
this.units.add(renderUnit);
|
||||||
if (unitShadowSplat != null) {
|
if (unitShadowSplat != null) {
|
||||||
unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() {
|
unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() {
|
||||||
@ -782,10 +872,10 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
for (final RenderUnit unit : this.units) {
|
for (final RenderUnit unit : this.units) {
|
||||||
unit.updateAnimations(this);
|
unit.updateAnimations(this);
|
||||||
}
|
}
|
||||||
final Iterator<RenderAttackProjectile> projectileIterator = this.projectiles.iterator();
|
final Iterator<RenderEffect> projectileIterator = this.projectiles.iterator();
|
||||||
while (projectileIterator.hasNext()) {
|
while (projectileIterator.hasNext()) {
|
||||||
final RenderAttackProjectile projectile = projectileIterator.next();
|
final RenderEffect projectile = projectileIterator.next();
|
||||||
if (projectile.updateAnimations(this)) {
|
if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) {
|
||||||
projectileIterator.remove();
|
projectileIterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -793,7 +883,7 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
final MdxComplexInstance instance = item.instance;
|
final MdxComplexInstance instance = item.instance;
|
||||||
final MdxComplexInstance mdxComplexInstance = instance;
|
final MdxComplexInstance mdxComplexInstance = instance;
|
||||||
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
|
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
|
||||||
StandSequence.randomStandSequence(mdxComplexInstance);
|
SequenceUtils.randomStandSequence(mdxComplexInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final Doodad item : this.doodads) {
|
for (final Doodad item : this.doodads) {
|
||||||
@ -801,12 +891,13 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) {
|
if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) {
|
||||||
final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance;
|
final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance;
|
||||||
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
|
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
|
||||||
StandSequence.randomStandSequence(mdxComplexInstance);
|
SequenceUtils.randomStandSequence(mdxComplexInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateTime += Gdx.graphics.getRawDeltaTime();
|
final float rawDeltaTime = Gdx.graphics.getRawDeltaTime();
|
||||||
|
this.updateTime += rawDeltaTime;
|
||||||
while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) {
|
while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) {
|
||||||
this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME;
|
this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME;
|
||||||
this.simulation.update();
|
this.simulation.update();
|
||||||
@ -942,7 +1033,8 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
for (final RenderUnit unit : this.units) {
|
for (final RenderUnit unit : this.units) {
|
||||||
final MdxComplexInstance instance = unit.instance;
|
final MdxComplexInstance instance = unit.instance;
|
||||||
if (instance.isVisible(this.worldScene.camera)
|
if (instance.isVisible(this.worldScene.camera)
|
||||||
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) {
|
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)
|
||||||
|
&& !unit.getSimulationUnit().isDead()) {
|
||||||
entity = unit;
|
entity = unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1081,7 +1173,6 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget();
|
final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget();
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false);
|
ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false);
|
||||||
unit.soundset.yes.play(this.worldScene.audioContext, unit.location[0], unit.location[1]);
|
|
||||||
ordered = true;
|
ordered = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1114,8 +1205,6 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget();
|
final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget();
|
||||||
if (targetWidget != null) {
|
if (targetWidget != null) {
|
||||||
ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false);
|
ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false);
|
||||||
unit.soundset.yesAttack.play(this.worldScene.audioContext, unit.location[0],
|
|
||||||
unit.location[1]);
|
|
||||||
ordered = true;
|
ordered = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1136,8 +1225,8 @@ public class War3MapViewer extends ModelViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void standOnRepeat(final MdxComplexInstance instance) {
|
public void standOnRepeat(final MdxComplexInstance instance) {
|
||||||
instance.setSequenceLoopMode(2);
|
instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
|
||||||
StandSequence.randomStandSequence(instance);
|
SequenceUtils.randomStandSequence(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class SelectionCircleSize {
|
private static final class SelectionCircleSize {
|
||||||
|
@ -31,8 +31,9 @@ public class PathingGrid {
|
|||||||
// this blit function is basically copied from HiveWE, maybe remember to mention
|
// this blit function is basically copied from HiveWE, maybe remember to mention
|
||||||
// that in credits as well:
|
// that in credits as well:
|
||||||
// https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp
|
// https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp
|
||||||
public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotation,
|
public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput,
|
||||||
final BufferedImage pathingTextureTga) {
|
final BufferedImage pathingTextureTga) {
|
||||||
|
final int rotation = (rotationInput + 450) % 360;
|
||||||
final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth();
|
final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth();
|
||||||
final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight();
|
final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight();
|
||||||
for (int j = 0; j < pathingTextureTga.getHeight(); j++) {
|
for (int j = 0; j < pathingTextureTga.getHeight(); j++) {
|
||||||
@ -65,13 +66,13 @@ public class PathingGrid {
|
|||||||
|
|
||||||
final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j);
|
final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j);
|
||||||
byte data = 0;
|
byte data = 0;
|
||||||
if ((rgb & 0xFF) > 250) {
|
if ((rgb & 0xFF) > 127) {
|
||||||
data |= PathingFlags.UNBUILDABLE;
|
data |= PathingFlags.UNBUILDABLE;
|
||||||
}
|
}
|
||||||
if (((rgb & 0xFF00) >> 8) > 250) {
|
if (((rgb & 0xFF00) >>> 8) > 127) {
|
||||||
data |= PathingFlags.UNFLYABLE;
|
data |= PathingFlags.UNFLYABLE;
|
||||||
}
|
}
|
||||||
if (((rgb & 0xFF0000) >> 16) > 250) {
|
if (((rgb & 0xFF0000) >>> 16) > 127) {
|
||||||
data |= PathingFlags.UNWALKABLE;
|
data |= PathingFlags.UNWALKABLE;
|
||||||
}
|
}
|
||||||
this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data;
|
this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data;
|
||||||
|
@ -383,6 +383,8 @@ public class Terrain {
|
|||||||
(this.mapBounds[2] * 128.0f) + this.centerOffset[1],
|
(this.mapBounds[2] * 128.0f) + this.centerOffset[1],
|
||||||
((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0],
|
((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0],
|
||||||
((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] };
|
((this.rows - this.mapBounds[3] - 1) * 128.0f) + this.centerOffset[1] };
|
||||||
|
this.shaderMapBoundsRectangle = new Rectangle(this.shaderMapBounds[0], this.shaderMapBounds[1],
|
||||||
|
this.shaderMapBounds[2] - this.shaderMapBounds[0], this.shaderMapBounds[3] - this.shaderMapBounds[1]);
|
||||||
this.mapSize = w3eFile.getMapSize();
|
this.mapSize = w3eFile.getMapSize();
|
||||||
this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights,
|
this.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights,
|
||||||
this.centerOffset, width, height);
|
this.centerOffset, width, height);
|
||||||
@ -1184,6 +1186,7 @@ public class Terrain {
|
|||||||
static Vector3 tmp3 = new Vector3();
|
static Vector3 tmp3 = new Vector3();
|
||||||
private final WaveBuilder waveBuilder;
|
private final WaveBuilder waveBuilder;
|
||||||
public PathingGrid pathingGrid;
|
public PathingGrid pathingGrid;
|
||||||
|
private final Rectangle shaderMapBoundsRectangle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Intersects the given ray with list of triangles. Returns the nearest
|
* Intersects the given ray with list of triangles. Returns the nearest
|
||||||
@ -1392,4 +1395,8 @@ public class Terrain {
|
|||||||
} // TODO why do we use floor if we can use int cast?
|
} // TODO why do we use floor if we can use int cast?
|
||||||
return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0;
|
return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Rectangle getPlayableMapArea() {
|
||||||
|
return this.shaderMapBoundsRectangle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
|
||||||
|
|
||||||
|
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.mdx.SequenceLoopMode;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
||||||
|
|
||||||
|
public class RenderAttackInstant implements RenderEffect {
|
||||||
|
private final MdxComplexInstance modelInstance;
|
||||||
|
|
||||||
|
public RenderAttackInstant(final MdxComplexInstance modelInstance, final War3MapViewer war3MapViewer,
|
||||||
|
final float yaw) {
|
||||||
|
this.modelInstance = modelInstance;
|
||||||
|
final MdxModel model = (MdxModel) this.modelInstance.model;
|
||||||
|
final List<Sequence> sequences = model.getSequences();
|
||||||
|
final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY, sequences,
|
||||||
|
true);
|
||||||
|
if (sequence != null) {
|
||||||
|
this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP);
|
||||||
|
this.modelInstance.setSequence(sequence.index);
|
||||||
|
}
|
||||||
|
this.modelInstance.localRotation.setFromAxisRad(0, 0, 1, yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateAnimations(final War3MapViewer war3MapViewer, final float deltaTime) {
|
||||||
|
|
||||||
|
final boolean everythingDone = this.modelInstance.sequenceEnded;
|
||||||
|
if (everythingDone) {
|
||||||
|
war3MapViewer.worldScene.removeInstance(this.modelInstance);
|
||||||
|
}
|
||||||
|
return everythingDone;
|
||||||
|
}
|
||||||
|
}
|
@ -2,17 +2,19 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
|
||||||
import com.badlogic.gdx.math.Quaternion;
|
import com.badlogic.gdx.math.Quaternion;
|
||||||
import com.etheller.warsmash.parsers.mdlx.Sequence;
|
import com.etheller.warsmash.parsers.mdlx.Sequence;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;
|
import com.etheller.warsmash.viewer5.handlers.w3x.IndexedSequence;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence;
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
||||||
|
|
||||||
public class RenderAttackProjectile {
|
public class RenderAttackProjectile implements RenderEffect {
|
||||||
private static final Quaternion pitchHeap = new Quaternion();
|
private static final Quaternion pitchHeap = new Quaternion();
|
||||||
|
|
||||||
private final CAttackProjectile simulationProjectile;
|
private final CAttackProjectile simulationProjectile;
|
||||||
@ -29,6 +31,8 @@ public class RenderAttackProjectile {
|
|||||||
private float yaw;
|
private float yaw;
|
||||||
|
|
||||||
private float pitch;
|
private float pitch;
|
||||||
|
private boolean done = false;
|
||||||
|
private float deathTimeElapsed;
|
||||||
|
|
||||||
public RenderAttackProjectile(final CAttackProjectile simulationProjectile, final MdxComplexInstance modelInstance,
|
public RenderAttackProjectile(final CAttackProjectile simulationProjectile, final MdxComplexInstance modelInstance,
|
||||||
final float z, final float arc, final War3MapViewer war3MapViewer) {
|
final float z, final float arc, final War3MapViewer war3MapViewer) {
|
||||||
@ -44,25 +48,32 @@ public class RenderAttackProjectile {
|
|||||||
final float dyToTarget = targetY - this.y;
|
final float dyToTarget = targetY - this.y;
|
||||||
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
|
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
|
||||||
final float startingDistance = d2DToTarget + this.totalTravelDistance;
|
final float startingDistance = d2DToTarget + this.totalTravelDistance;
|
||||||
|
float impactZ = this.simulationProjectile.getTarget().getImpactZ();
|
||||||
|
if (simulationProjectile.getUnitAttack().getWeaponType() == CWeaponType.ARTILLERY) {
|
||||||
|
impactZ = 0;
|
||||||
|
}
|
||||||
this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY)
|
this.targetHeight = (war3MapViewer.terrain.getGroundHeight(targetX, targetY)
|
||||||
+ this.simulationProjectile.getTarget().getFlyHeight()
|
+ this.simulationProjectile.getTarget().getFlyHeight() + impactZ);
|
||||||
+ this.simulationProjectile.getTarget().getImpactZ());
|
|
||||||
this.arcPeakHeight = arc * startingDistance;
|
this.arcPeakHeight = arc * startingDistance;
|
||||||
this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget);
|
this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean updateAnimations(final War3MapViewer war3MapViewer) {
|
@Override
|
||||||
if (this.simulationProjectile.isDone()) {
|
public boolean updateAnimations(final War3MapViewer war3MapViewer, final float deltaTime) {
|
||||||
|
final boolean wasDone = this.done;
|
||||||
|
if (this.done = this.simulationProjectile.isDone()) {
|
||||||
final MdxModel model = (MdxModel) this.modelInstance.model;
|
final MdxModel model = (MdxModel) this.modelInstance.model;
|
||||||
final List<Sequence> sequences = model.getSequences();
|
final List<Sequence> sequences = model.getSequences();
|
||||||
final IndexedSequence sequence = StandSequence.selectSequence("death", sequences);
|
final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY,
|
||||||
if ((sequence != null) && (this.modelInstance.sequence != sequence.index)) {
|
sequences, true);
|
||||||
|
if ((sequence != null) && this.done && !wasDone) {
|
||||||
|
this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP);
|
||||||
this.modelInstance.setSequence(sequence.index);
|
this.modelInstance.setSequence(sequence.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) {
|
if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) {
|
||||||
StandSequence.randomStandSequence(this.modelInstance);
|
SequenceUtils.randomStandSequence(this.modelInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final float simX = this.simulationProjectile.getX();
|
final float simX = this.simulationProjectile.getX();
|
||||||
@ -70,13 +81,12 @@ public class RenderAttackProjectile {
|
|||||||
final float simDx = simX - this.x;
|
final float simDx = simX - this.x;
|
||||||
final float simDy = simY - this.y;
|
final float simDy = simY - this.y;
|
||||||
final float simD = (float) StrictMath.sqrt((simDx * simDx) + (simDy * simDy));
|
final float simD = (float) StrictMath.sqrt((simDx * simDx) + (simDy * simDy));
|
||||||
final float deltaTime = Gdx.graphics.getDeltaTime();
|
|
||||||
final float speed = StrictMath.min(simD, this.simulationProjectile.getSpeed() * deltaTime);
|
final float speed = StrictMath.min(simD, this.simulationProjectile.getSpeed() * deltaTime);
|
||||||
if (simD > 0) {
|
if (simD > 0) {
|
||||||
this.x = this.x + ((speed * simDx) / simD);
|
this.x = this.x + ((speed * simDx) / simD);
|
||||||
this.y = this.y + ((speed * simDy) / simD);
|
this.y = this.y + ((speed * simDy) / simD);
|
||||||
final float targetX = this.simulationProjectile.getTarget().getX();
|
final float targetX = this.simulationProjectile.getTargetX();
|
||||||
final float targetY = this.simulationProjectile.getTarget().getY();
|
final float targetY = this.simulationProjectile.getTargetY();
|
||||||
final float dxToTarget = targetX - this.x;
|
final float dxToTarget = targetX - this.x;
|
||||||
final float dyToTarget = targetY - this.y;
|
final float dyToTarget = targetY - this.y;
|
||||||
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
|
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
|
||||||
@ -94,10 +104,16 @@ public class RenderAttackProjectile {
|
|||||||
final float arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight;
|
final float arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight;
|
||||||
this.z = this.startingHeight + dz + arcCurrentHeight;
|
this.z = this.startingHeight + dz + arcCurrentHeight;
|
||||||
|
|
||||||
this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget);
|
if (!this.done) {
|
||||||
|
this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget);
|
||||||
|
|
||||||
final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance;
|
final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance;
|
||||||
this.pitch = (float) StrictMath.atan2(slope + d1z, 1);
|
this.pitch = (float) StrictMath.atan2(slope + d1z, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.done) {
|
||||||
|
this.pitch = 0;
|
||||||
|
this.deathTimeElapsed += deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modelInstance.setLocation(this.x, this.y, this.z);
|
this.modelInstance.setLocation(this.x, this.y, this.z);
|
||||||
@ -105,7 +121,8 @@ public class RenderAttackProjectile {
|
|||||||
this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch));
|
this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch));
|
||||||
war3MapViewer.worldScene.instanceMoved(this.modelInstance, this.x, this.y);
|
war3MapViewer.worldScene.instanceMoved(this.modelInstance, this.x, this.y);
|
||||||
|
|
||||||
final boolean everythingDone = this.simulationProjectile.isDone() && this.modelInstance.sequenceEnded;
|
final boolean everythingDone = this.simulationProjectile.isDone() && (this.modelInstance.sequenceEnded
|
||||||
|
|| (this.deathTimeElapsed >= war3MapViewer.simulation.getGameplayConstants().getBulletDeathTime()));
|
||||||
if (everythingDone) {
|
if (everythingDone) {
|
||||||
war3MapViewer.worldScene.removeInstance(this.modelInstance);
|
war3MapViewer.worldScene.removeInstance(this.modelInstance);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
||||||
|
|
||||||
|
public interface RenderEffect {
|
||||||
|
boolean updateAnimations(final War3MapViewer war3MapViewer, float deltaTime);
|
||||||
|
}
|
@ -8,6 +8,7 @@ import java.util.Queue;
|
|||||||
|
|
||||||
import com.badlogic.gdx.Gdx;
|
import com.badlogic.gdx.Gdx;
|
||||||
import com.badlogic.gdx.math.Quaternion;
|
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.units.manager.MutableObjectData.MutableGameObject;
|
||||||
import com.etheller.warsmash.util.ImageUtils;
|
import com.etheller.warsmash.util.ImageUtils;
|
||||||
import com.etheller.warsmash.util.RenderMathUtils;
|
import com.etheller.warsmash.util.RenderMathUtils;
|
||||||
@ -17,8 +18,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover;
|
import com.etheller.warsmash.viewer5.handlers.w3x.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.UnitSoundset;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
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;
|
||||||
@ -40,6 +41,7 @@ public class RenderUnit {
|
|||||||
private static final War3ID MODEL_SCALE = War3ID.fromString("usca");
|
private static final War3ID MODEL_SCALE = War3ID.fromString("usca");
|
||||||
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
|
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
|
||||||
private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori");
|
private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori");
|
||||||
|
private static final War3ID ANIM_PROPS = War3ID.fromString("uani");
|
||||||
private static final float[] heapZ = new float[3];
|
private static final float[] heapZ = new float[3];
|
||||||
public final MdxComplexInstance instance;
|
public final MdxComplexInstance instance;
|
||||||
public final MutableGameObject row;
|
public final MutableGameObject row;
|
||||||
@ -59,11 +61,14 @@ public class RenderUnit {
|
|||||||
|
|
||||||
private boolean swimming;
|
private boolean swimming;
|
||||||
|
|
||||||
private final boolean alreadyPlayedDeath = false;
|
private boolean dead = false;
|
||||||
|
|
||||||
private final UnitAnimationListenerImpl unitAnimationListenerImpl;
|
private final UnitAnimationListenerImpl unitAnimationListenerImpl;
|
||||||
private OrientationInterpolation orientationInterpolation;
|
private OrientationInterpolation orientationInterpolation;
|
||||||
private float currentTurnVelocity = 0;
|
private float currentTurnVelocity = 0;
|
||||||
|
public long lastUnitResponseEndTimeMillis;
|
||||||
|
private boolean corpse;
|
||||||
|
private boolean boneCorpse;
|
||||||
|
|
||||||
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
|
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
|
||||||
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset,
|
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset,
|
||||||
@ -87,6 +92,16 @@ public class RenderUnit {
|
|||||||
instance.setScene(map.worldScene);
|
instance.setScene(map.worldScene);
|
||||||
this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance);
|
this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance);
|
||||||
simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl);
|
simulationUnit.setUnitAnimationListener(this.unitAnimationListenerImpl);
|
||||||
|
final String requiredAnimationNames = row.getFieldAsString(ANIM_PROPS, 0);
|
||||||
|
TokenLoop: for (final String animationName : requiredAnimationNames.split(",")) {
|
||||||
|
final String upperCaseToken = animationName.toUpperCase();
|
||||||
|
for (final SecondaryTag secondaryTag : SecondaryTag.values()) {
|
||||||
|
if (upperCaseToken.equals(secondaryTag.name())) {
|
||||||
|
this.unitAnimationListenerImpl.addSecondaryTag(secondaryTag);
|
||||||
|
continue TokenLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (row != null) {
|
if (row != null) {
|
||||||
heapZ[2] = simulationUnit.getFlyHeight();
|
heapZ[2] = simulationUnit.getFlyHeight();
|
||||||
@ -194,6 +209,31 @@ public class RenderUnit {
|
|||||||
this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
|
this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
|
||||||
}
|
}
|
||||||
this.swimming = swimming;
|
this.swimming = swimming;
|
||||||
|
final boolean dead = this.simulationUnit.isDead();
|
||||||
|
final boolean corpse = this.simulationUnit.isCorpse();
|
||||||
|
final boolean boneCorpse = this.simulationUnit.isBoneCorpse();
|
||||||
|
if (dead && !this.dead) {
|
||||||
|
this.unitAnimationListenerImpl.playAnimation(true, PrimaryTag.DEATH, SequenceUtils.EMPTY, 1.0f, true);
|
||||||
|
if (this.shadow != null) {
|
||||||
|
this.shadow.destroy(Gdx.gl30, map.terrain.centerOffset);
|
||||||
|
this.shadow = null;
|
||||||
|
}
|
||||||
|
if (this.selectionCircle != null) {
|
||||||
|
this.selectionCircle.destroy(Gdx.gl30, map.terrain.centerOffset);
|
||||||
|
this.selectionCircle = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (boneCorpse && !this.boneCorpse) {
|
||||||
|
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.BONE,
|
||||||
|
map.simulation.getGameplayConstants().getBoneDecayTime(), true);
|
||||||
|
}
|
||||||
|
else if (corpse && !this.corpse) {
|
||||||
|
this.unitAnimationListenerImpl.playAnimationWithDuration(true, PrimaryTag.DECAY, SequenceUtils.FLESH,
|
||||||
|
map.simulation.getGameplayConstants().getDecayTime(), true);
|
||||||
|
}
|
||||||
|
this.dead = dead;
|
||||||
|
this.corpse = corpse;
|
||||||
|
this.boneCorpse = boneCorpse;
|
||||||
this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight;
|
this.location[2] = this.simulationUnit.getFlyHeight() + groundHeight;
|
||||||
this.instance.moveTo(this.location);
|
this.instance.moveTo(this.location);
|
||||||
float simulationFacing = this.simulationUnit.getFacing();
|
float simulationFacing = this.simulationUnit.getFacing();
|
||||||
@ -266,6 +306,7 @@ public class RenderUnit {
|
|||||||
private PrimaryTag currentAnimation;
|
private PrimaryTag currentAnimation;
|
||||||
private EnumSet<SecondaryTag> currentAnimationSecondaryTags;
|
private EnumSet<SecondaryTag> currentAnimationSecondaryTags;
|
||||||
private float currentSpeedRatio;
|
private float currentSpeedRatio;
|
||||||
|
private boolean currentlyAllowingRarityVariations;
|
||||||
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
|
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
|
||||||
|
|
||||||
public UnitAnimationListenerImpl(final MdxComplexInstance instance) {
|
public UnitAnimationListenerImpl(final MdxComplexInstance instance) {
|
||||||
@ -274,33 +315,59 @@ public class RenderUnit {
|
|||||||
|
|
||||||
public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) {
|
public void addSecondaryTag(final AnimationTokens.SecondaryTag tag) {
|
||||||
this.secondaryAnimationTags.add(tag);
|
this.secondaryAnimationTags.add(tag);
|
||||||
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio);
|
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio,
|
||||||
|
this.currentlyAllowingRarityVariations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) {
|
public void removeSecondaryTag(final AnimationTokens.SecondaryTag tag) {
|
||||||
this.secondaryAnimationTags.remove(tag);
|
this.secondaryAnimationTags.remove(tag);
|
||||||
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio);
|
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio,
|
||||||
|
this.currentlyAllowingRarityVariations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void playAnimation(final boolean force, final PrimaryTag animationName,
|
public void playAnimation(final boolean force, final PrimaryTag animationName,
|
||||||
final EnumSet<SecondaryTag> secondaryAnimationTags, final float speedRatio) {
|
final EnumSet<SecondaryTag> secondaryAnimationTags, final float speedRatio,
|
||||||
|
final boolean allowRarityVariations) {
|
||||||
this.animationQueue.clear();
|
this.animationQueue.clear();
|
||||||
if (force || (animationName != this.currentAnimation)) {
|
if (force || (animationName != this.currentAnimation)) {
|
||||||
this.currentAnimation = animationName;
|
this.currentAnimation = animationName;
|
||||||
this.currentAnimationSecondaryTags = secondaryAnimationTags;
|
this.currentAnimationSecondaryTags = secondaryAnimationTags;
|
||||||
this.currentSpeedRatio = speedRatio;
|
this.currentSpeedRatio = speedRatio;
|
||||||
|
this.currentlyAllowingRarityVariations = allowRarityVariations;
|
||||||
this.recycleSet.clear();
|
this.recycleSet.clear();
|
||||||
this.recycleSet.addAll(this.secondaryAnimationTags);
|
this.recycleSet.addAll(this.secondaryAnimationTags);
|
||||||
this.recycleSet.addAll(secondaryAnimationTags);
|
this.recycleSet.addAll(secondaryAnimationTags);
|
||||||
this.instance.setAnimationSpeed(speedRatio);
|
this.instance.setAnimationSpeed(speedRatio);
|
||||||
StandSequence.randomSequence(this.instance, animationName, this.recycleSet);
|
SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void playAnimationWithDuration(final boolean force, final PrimaryTag animationName,
|
||||||
|
final EnumSet<SecondaryTag> secondaryAnimationTags, final float duration,
|
||||||
|
final boolean allowRarityVariations) {
|
||||||
|
this.animationQueue.clear();
|
||||||
|
if (force || (animationName != this.currentAnimation)) {
|
||||||
|
this.currentAnimation = animationName;
|
||||||
|
this.currentAnimationSecondaryTags = secondaryAnimationTags;
|
||||||
|
this.currentlyAllowingRarityVariations = allowRarityVariations;
|
||||||
|
this.recycleSet.clear();
|
||||||
|
this.recycleSet.addAll(this.secondaryAnimationTags);
|
||||||
|
this.recycleSet.addAll(secondaryAnimationTags);
|
||||||
|
final Sequence sequence = SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet,
|
||||||
|
allowRarityVariations);
|
||||||
|
if (sequence != null) {
|
||||||
|
this.currentSpeedRatio = ((sequence.getInterval()[1] - sequence.getInterval()[0]) / 1000.0f)
|
||||||
|
/ duration;
|
||||||
|
this.instance.setAnimationSpeed(this.currentSpeedRatio);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags) {
|
public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
|
||||||
this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags));
|
final boolean allowRarityVariations) {
|
||||||
|
this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
@ -310,12 +377,13 @@ public class RenderUnit {
|
|||||||
.get(this.instance.sequence).getFlags() == 0)) {
|
.get(this.instance.sequence).getFlags() == 0)) {
|
||||||
// animation is a looping animation
|
// animation is a looping animation
|
||||||
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags,
|
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags,
|
||||||
this.currentSpeedRatio);
|
this.currentSpeedRatio, this.currentlyAllowingRarityVariations);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final QueuedAnimation nextAnimation = this.animationQueue.poll();
|
final QueuedAnimation nextAnimation = this.animationQueue.poll();
|
||||||
if (nextAnimation != null) {
|
if (nextAnimation != null) {
|
||||||
playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f);
|
playAnimation(true, nextAnimation.animationName, nextAnimation.secondaryAnimationTags, 1.0f,
|
||||||
|
nextAnimation.allowRarityVariations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,10 +394,13 @@ public class RenderUnit {
|
|||||||
private static final class QueuedAnimation {
|
private static final class QueuedAnimation {
|
||||||
private final PrimaryTag animationName;
|
private final PrimaryTag animationName;
|
||||||
private final EnumSet<SecondaryTag> secondaryAnimationTags;
|
private final EnumSet<SecondaryTag> secondaryAnimationTags;
|
||||||
|
private final boolean allowRarityVariations;
|
||||||
|
|
||||||
public QueuedAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags) {
|
public QueuedAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
|
||||||
|
final boolean allowRarityVariations) {
|
||||||
this.animationName = animationName;
|
this.animationName = animationName;
|
||||||
this.secondaryAnimationTags = secondaryAnimationTags;
|
this.secondaryAnimationTags = secondaryAnimationTags;
|
||||||
|
this.allowRarityVariations = allowRarityVariations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
|
|
||||||
public class CDestructable extends CWidget {
|
public class CDestructable extends CWidget {
|
||||||
|
|
||||||
public CDestructable(final int handleId, final float x, final float y, final float life) {
|
public CDestructable(final int handleId, final float x, final float y, final float life) {
|
||||||
@ -15,4 +20,16 @@ public class CDestructable extends CWidget {
|
|||||||
public float getImpactZ() {
|
public float getImpactZ() {
|
||||||
return 0; // TODO maybe from DestructableType
|
return 0; // TODO maybe from DestructableType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
|
||||||
|
final String weaponType, final float damage) {
|
||||||
|
this.life -= damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source,
|
||||||
|
final EnumSet<CTargetType> targetsAllowed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import com.etheller.warsmash.units.DataTable;
|
import com.etheller.warsmash.units.DataTable;
|
||||||
import com.etheller.warsmash.units.Element;
|
import com.etheller.warsmash.units.Element;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores some gameplay constants at runtime in a java object (symbol table) to
|
* Stores some gameplay constants at runtime in a java object (symbol table) to
|
||||||
@ -9,13 +13,71 @@ import com.etheller.warsmash.units.Element;
|
|||||||
*/
|
*/
|
||||||
public class CGameplayConstants {
|
public class CGameplayConstants {
|
||||||
private final float attackHalfAngle;
|
private final float attackHalfAngle;
|
||||||
|
private final float[][] damageBonusTable;
|
||||||
|
private final float maxCollisionRadius;
|
||||||
|
private final float decayTime;
|
||||||
|
private final float boneDecayTime;
|
||||||
|
private final float bulletDeathTime;
|
||||||
|
private final float closeEnoughRange;
|
||||||
|
|
||||||
public CGameplayConstants(final DataTable parsedDataTable) {
|
public CGameplayConstants(final DataTable parsedDataTable) {
|
||||||
final Element miscData = parsedDataTable.get("Misc");
|
final Element miscData = parsedDataTable.get("Misc");
|
||||||
this.attackHalfAngle = (miscData.getFieldFloatValue("AttackHalfAngle")); // TODO use
|
// TODO use radians for half angle
|
||||||
|
this.attackHalfAngle = (float) Math.toDegrees(miscData.getFieldFloatValue("AttackHalfAngle"));
|
||||||
|
this.maxCollisionRadius = miscData.getFieldFloatValue("MaxCollisionRadius");
|
||||||
|
this.decayTime = miscData.getFieldFloatValue("DecayTime");
|
||||||
|
this.boneDecayTime = miscData.getFieldFloatValue("BoneDecayTime");
|
||||||
|
this.bulletDeathTime = miscData.getFieldFloatValue("BulletDeathTime");
|
||||||
|
this.closeEnoughRange = miscData.getFieldFloatValue("CloseEnoughRange");
|
||||||
|
|
||||||
|
final CDefenseType[] defenseTypeOrder = { CDefenseType.SMALL, CDefenseType.MEDIUM, CDefenseType.LARGE,
|
||||||
|
CDefenseType.FORT, CDefenseType.NORMAL, CDefenseType.HERO, CDefenseType.DIVINE, CDefenseType.NONE, };
|
||||||
|
this.damageBonusTable = new float[CAttackType.values().length][defenseTypeOrder.length];
|
||||||
|
for (int i = 0; i < CAttackType.VALUES.length; i++) {
|
||||||
|
Arrays.fill(this.damageBonusTable[i], 1.0f);
|
||||||
|
final CAttackType attackType = CAttackType.VALUES[i];
|
||||||
|
final String damageBonus = miscData.getField("DamageBonus" + attackType.getDamageKey());
|
||||||
|
final String[] damageComponents = damageBonus.split(",");
|
||||||
|
for (int j = 0; j < damageComponents.length; j++) {
|
||||||
|
if (damageComponents[j].length() > 0) {
|
||||||
|
final CDefenseType defenseType = defenseTypeOrder[j];
|
||||||
|
try {
|
||||||
|
this.damageBonusTable[i][defenseType.ordinal()] = Float.parseFloat(damageComponents[j]);
|
||||||
|
// System.out.println(attackType + ":" + defenseType + ": " + damageComponents[j]);
|
||||||
|
}
|
||||||
|
catch (final NumberFormatException e) {
|
||||||
|
throw new RuntimeException("DamageBonus" + attackType.getDamageKey(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getAttackHalfAngle() {
|
public float getAttackHalfAngle() {
|
||||||
return this.attackHalfAngle;
|
return this.attackHalfAngle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getDamageRatioAgainst(final CAttackType attackType, final CDefenseType defenseType) {
|
||||||
|
return this.damageBonusTable[attackType.ordinal()][defenseType.ordinal()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getMaxCollisionRadius() {
|
||||||
|
return this.maxCollisionRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDecayTime() {
|
||||||
|
return this.decayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBoneDecayTime() {
|
||||||
|
return this.boneDecayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBulletDeathTime() {
|
||||||
|
return this.bulletDeathTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCloseEnoughRange() {
|
||||||
|
return this.closeEnoughRange;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import com.etheller.warsmash.util.War3ID;
|
import com.etheller.warsmash.util.War3ID;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
|
|
||||||
public class CItem extends CWidget {
|
public class CItem extends CWidget {
|
||||||
|
|
||||||
@ -20,4 +24,16 @@ public class CItem extends CWidget {
|
|||||||
public float getImpactZ() {
|
public float getImpactZ() {
|
||||||
return 0; // TODO probably from ItemType
|
return 0; // TODO probably from ItemType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
|
||||||
|
final String weaponType, final float damage) {
|
||||||
|
this.life -= damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source,
|
||||||
|
final EnumSet<CTargetType> targetsAllowed) {
|
||||||
|
return targetsAllowed.contains(CTargetType.ITEM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
|
||||||
|
|
||||||
public class CPlayer {
|
|
||||||
private int id;
|
|
||||||
private int gold;
|
|
||||||
private int lumber;
|
|
||||||
|
|
||||||
public CPlayer(final int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
|
||||||
return this.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGold() {
|
|
||||||
return this.gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLumber() {
|
|
||||||
return this.lumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(final int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGold(final int gold) {
|
|
||||||
this.gold = gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLumber(final int lumber) {
|
|
||||||
this.lumber = lumber;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +1,82 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Rectangle;
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
|
import com.etheller.warsmash.parsers.w3x.w3i.Player;
|
||||||
import com.etheller.warsmash.units.DataTable;
|
import com.etheller.warsmash.units.DataTable;
|
||||||
import com.etheller.warsmash.units.manager.MutableObjectData;
|
import com.etheller.warsmash.units.manager.MutableObjectData;
|
||||||
import com.etheller.warsmash.util.War3ID;
|
import com.etheller.warsmash.util.War3ID;
|
||||||
|
import com.etheller.warsmash.util.WarsmashConstants;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
|
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData;
|
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.data.CUnitData;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController;
|
||||||
|
|
||||||
public class CSimulation {
|
public class CSimulation {
|
||||||
private final CUnitData unitData;
|
private final CUnitData unitData;
|
||||||
private final CAbilityData abilityData;
|
private final CAbilityData abilityData;
|
||||||
private final List<CUnit> units;
|
private final List<CUnit> units;
|
||||||
|
private final List<CPlayer> players;
|
||||||
private final List<CAttackProjectile> projectiles;
|
private final List<CAttackProjectile> projectiles;
|
||||||
|
private final List<CAttackProjectile> newProjectiles;
|
||||||
private final HandleIdAllocator handleIdAllocator;
|
private final HandleIdAllocator handleIdAllocator;
|
||||||
private transient final ProjectileCreator projectileCreator;
|
private transient final SimulationRenderController simulationRenderController;
|
||||||
private int gameTurnTick = 0;
|
private int gameTurnTick = 0;
|
||||||
private final PathingGrid pathingGrid;
|
private final PathingGrid pathingGrid;
|
||||||
private final CWorldCollision worldCollision;
|
private final CWorldCollision worldCollision;
|
||||||
private final CPathfindingProcessor pathfindingProcessor;
|
private final CPathfindingProcessor pathfindingProcessor;
|
||||||
private final CGameplayConstants gameplayConstants;
|
private final CGameplayConstants gameplayConstants;
|
||||||
|
private final Random seededRandom;
|
||||||
|
|
||||||
public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData,
|
public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData,
|
||||||
final MutableObjectData parsedAbilityData, final ProjectileCreator projectileCreator,
|
final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController,
|
||||||
final PathingGrid pathingGrid, final Rectangle entireMapBounds) {
|
final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom,
|
||||||
|
final List<Player> playerInfos) {
|
||||||
this.gameplayConstants = new CGameplayConstants(miscData);
|
this.gameplayConstants = new CGameplayConstants(miscData);
|
||||||
this.projectileCreator = projectileCreator;
|
this.simulationRenderController = simulationRenderController;
|
||||||
this.pathingGrid = pathingGrid;
|
this.pathingGrid = pathingGrid;
|
||||||
this.unitData = new CUnitData(parsedUnitData);
|
this.unitData = new CUnitData(parsedUnitData);
|
||||||
this.abilityData = new CAbilityData(parsedAbilityData);
|
this.abilityData = new CAbilityData(parsedAbilityData);
|
||||||
this.units = new ArrayList<>();
|
this.units = new ArrayList<>();
|
||||||
this.projectiles = new ArrayList<>();
|
this.projectiles = new ArrayList<>();
|
||||||
|
this.newProjectiles = new ArrayList<>();
|
||||||
this.handleIdAllocator = new HandleIdAllocator();
|
this.handleIdAllocator = new HandleIdAllocator();
|
||||||
this.worldCollision = new CWorldCollision(entireMapBounds);
|
this.worldCollision = new CWorldCollision(entireMapBounds, this.gameplayConstants.getMaxCollisionRadius());
|
||||||
this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision);
|
this.pathfindingProcessor = new CPathfindingProcessor(pathingGrid, this.worldCollision);
|
||||||
|
this.seededRandom = seededRandom;
|
||||||
|
this.players = new ArrayList<>();
|
||||||
|
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
|
||||||
|
if (i < playerInfos.size()) {
|
||||||
|
final Player playerInfo = playerInfos.get(i);
|
||||||
|
this.players.add(new CPlayer(playerInfo.getId().getValue(), CMapControl.values()[playerInfo.getType()],
|
||||||
|
playerInfo.getName(), CRace.parseRace(playerInfo.getRace()), playerInfo.getStartLocation()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.players.add(new CPlayer(i, CMapControl.NONE, "Default string", CRace.OTHER, new float[] { 0, 0 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL,
|
||||||
|
miscData.getLocalizedString("WESTRING_PLAYER_NA"), CRace.OTHER, new float[] { 0, 0 }));
|
||||||
|
this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL,
|
||||||
|
miscData.getLocalizedString("WESTRING_PLAYER_NV"), CRace.OTHER, new float[] { 0, 0 }));
|
||||||
|
this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL,
|
||||||
|
miscData.getLocalizedString("WESTRING_PLAYER_NE"), CRace.OTHER, new float[] { 0, 0 }));
|
||||||
|
this.players.add(new CPlayer(this.players.size(), CMapControl.NEUTRAL,
|
||||||
|
miscData.getLocalizedString("WESTRING_PLAYER_NP"), CRace.OTHER, new float[] { 0, 0 }));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CUnitData getUnitData() {
|
public CUnitData getUnitData() {
|
||||||
@ -57,36 +92,48 @@ public class CSimulation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y,
|
public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y,
|
||||||
final float facing) {
|
final float facing, final BufferedImage buildingPathingPixelMap) {
|
||||||
final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y,
|
final CUnit unit = this.unitData.create(this, playerIndex, this.handleIdAllocator.createId(), typeId, x, y,
|
||||||
facing);
|
facing, buildingPathingPixelMap);
|
||||||
this.units.add(unit);
|
this.units.add(unit);
|
||||||
if (!unit.getUnitType().isBuilding()) {
|
this.worldCollision.addUnit(unit);
|
||||||
this.worldCollision.addUnit(unit);
|
|
||||||
}
|
|
||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CAttackProjectile createProjectile(final CUnit source, final int attackIndex, final CWidget target) {
|
public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY,
|
||||||
final CAttackProjectile projectile = this.projectileCreator.create(this, source, attackIndex, target);
|
final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage,
|
||||||
this.projectiles.add(projectile);
|
final int bounceIndex) {
|
||||||
|
final CAttackProjectile projectile = this.simulationRenderController.createAttackProjectile(this, launchX,
|
||||||
|
launchY, launchFacing, source, attack, target, damage, bounceIndex);
|
||||||
|
this.newProjectiles.add(projectile);
|
||||||
return projectile;
|
return projectile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void createInstantAttackEffect(final CUnit source, final CUnitAttackInstant attack, final CWidget target) {
|
||||||
|
this.simulationRenderController.createInstantAttackEffect(this, source, attack, target);
|
||||||
|
}
|
||||||
|
|
||||||
public PathingGrid getPathingGrid() {
|
public PathingGrid getPathingGrid() {
|
||||||
return this.pathingGrid;
|
return this.pathingGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
|
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit,
|
||||||
final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType,
|
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
|
||||||
final float collisionSize) {
|
final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
|
||||||
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, startX, startY, goal,
|
final boolean allowSmoothing) {
|
||||||
movementType, collisionSize);
|
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit,
|
||||||
|
ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize,
|
||||||
|
allowSmoothing);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update() {
|
public void update() {
|
||||||
for (final CUnit unit : this.units) {
|
final Iterator<CUnit> unitIterator = this.units.iterator();
|
||||||
unit.update(this);
|
while (unitIterator.hasNext()) {
|
||||||
|
final CUnit unit = unitIterator.next();
|
||||||
|
if (unit.update(this)) {
|
||||||
|
unitIterator.remove();
|
||||||
|
this.simulationRenderController.removeUnit(unit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final Iterator<CAttackProjectile> projectileIterator = this.projectiles.iterator();
|
final Iterator<CAttackProjectile> projectileIterator = this.projectiles.iterator();
|
||||||
while (projectileIterator.hasNext()) {
|
while (projectileIterator.hasNext()) {
|
||||||
@ -95,6 +142,8 @@ public class CSimulation {
|
|||||||
projectileIterator.remove();
|
projectileIterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.projectiles.addAll(this.newProjectiles);
|
||||||
|
this.newProjectiles.clear();
|
||||||
this.gameTurnTick++;
|
this.gameTurnTick++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,4 +158,16 @@ public class CSimulation {
|
|||||||
public CGameplayConstants getGameplayConstants() {
|
public CGameplayConstants getGameplayConstants() {
|
||||||
return this.gameplayConstants;
|
return this.gameplayConstants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Random getSeededRandom() {
|
||||||
|
return this.seededRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unitDamageEvent(final CUnit damagedUnit, final String weaponSound, final String armorType) {
|
||||||
|
this.simulationRenderController.spawnUnitDamageSound(damagedUnit, weaponSound, armorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CPlayer getPlayer(final int index) {
|
||||||
|
return this.players.get(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -8,10 +9,15 @@ import java.util.Queue;
|
|||||||
|
|
||||||
import com.badlogic.gdx.math.Rectangle;
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
import com.etheller.warsmash.util.War3ID;
|
import com.etheller.warsmash.util.War3ID;
|
||||||
|
import com.etheller.warsmash.util.WarsmashConstants;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener.CUnitStateNotifier;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer;
|
||||||
|
|
||||||
public class CUnit extends CWidget {
|
public class CUnit extends CWidget {
|
||||||
private War3ID typeId;
|
private War3ID typeId;
|
||||||
@ -35,12 +41,22 @@ public class CUnit extends CWidget {
|
|||||||
|
|
||||||
private final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
|
private final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
|
||||||
|
|
||||||
|
private int deathTurnTick;
|
||||||
|
private boolean corpse;
|
||||||
|
private boolean boneCorpse;
|
||||||
|
|
||||||
private transient CUnitAnimationListener unitAnimationListener;
|
private transient CUnitAnimationListener unitAnimationListener;
|
||||||
|
|
||||||
|
// if you use triggers for this then the transient tag here becomes really
|
||||||
|
// questionable -- it already was -- but I meant for those to inform us
|
||||||
|
// which fields shouldn't be persisted if we do game state save later
|
||||||
|
private transient CUnitStateNotifier stateNotifier = new CUnitStateNotifier();
|
||||||
|
|
||||||
public CUnit(final int handleId, final int playerIndex, final float x, final float y, final float life,
|
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 War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana,
|
||||||
final int speed, final int defense, final CUnitType unitType) {
|
final int speed, final int defense, final CUnitType unitType) {
|
||||||
super(handleId, x, y, life);
|
super(handleId, x, y, life);
|
||||||
|
this.playerIndex = playerIndex;
|
||||||
this.typeId = typeId;
|
this.typeId = typeId;
|
||||||
this.facing = facing;
|
this.facing = facing;
|
||||||
this.mana = mana;
|
this.mana = mana;
|
||||||
@ -55,7 +71,7 @@ public class CUnit extends CWidget {
|
|||||||
|
|
||||||
public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) {
|
public void setUnitAnimationListener(final CUnitAnimationListener unitAnimationListener) {
|
||||||
this.unitAnimationListener = unitAnimationListener;
|
this.unitAnimationListener = unitAnimationListener;
|
||||||
this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f);
|
this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CUnitAnimationListener getUnitAnimationListener() {
|
public CUnitAnimationListener getUnitAnimationListener() {
|
||||||
@ -121,10 +137,48 @@ public class CUnit extends CWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates one tick of simulation logic.
|
* Updates one tick of simulation logic and return true if it's time to remove
|
||||||
|
* this unit from the game.
|
||||||
*/
|
*/
|
||||||
public void update(final CSimulation game) {
|
public boolean update(final CSimulation game) {
|
||||||
if (this.currentOrder != null) {
|
if (isDead()) {
|
||||||
|
if (this.collisionRectangle != null) {
|
||||||
|
// Moved this here because doing it on "kill" was able to happen in some cases
|
||||||
|
// while also iterating over the units that are in the collision system, and
|
||||||
|
// then it hit the "writing while iterating" problem.
|
||||||
|
game.getWorldCollision().removeUnit(this);
|
||||||
|
}
|
||||||
|
final int gameTurnTick = game.getGameTurnTick();
|
||||||
|
if (!this.corpse) {
|
||||||
|
if (gameTurnTick > (this.deathTurnTick
|
||||||
|
+ (int) (this.unitType.getDeathTime() / WarsmashConstants.SIMULATION_STEP_TIME))) {
|
||||||
|
this.corpse = true;
|
||||||
|
if (!this.unitType.isRaise()) {
|
||||||
|
this.boneCorpse = true;
|
||||||
|
// start final phase immediately for "cant raise" case
|
||||||
|
}
|
||||||
|
if (!this.unitType.isDecay()) {
|
||||||
|
// if we dont raise AND dont decay, then now that death anim is over
|
||||||
|
// we just delete the unit
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
this.deathTurnTick = gameTurnTick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.boneCorpse) {
|
||||||
|
if (game.getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getDecayTime()
|
||||||
|
/ WarsmashConstants.SIMULATION_STEP_TIME))) {
|
||||||
|
this.boneCorpse = true;
|
||||||
|
this.deathTurnTick = gameTurnTick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (game
|
||||||
|
.getGameTurnTick() > (this.deathTurnTick + (int) (game.getGameplayConstants().getBoneDecayTime()
|
||||||
|
/ WarsmashConstants.SIMULATION_STEP_TIME))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.currentOrder != null) {
|
||||||
if (this.currentOrder.update(game)) {
|
if (this.currentOrder.update(game)) {
|
||||||
// remove current order, because it's completed, polling next
|
// remove current order, because it's completed, polling next
|
||||||
// item from order queue
|
// item from order queue
|
||||||
@ -132,12 +186,16 @@ public class CUnit extends CWidget {
|
|||||||
}
|
}
|
||||||
if (this.currentOrder == null) {
|
if (this.currentOrder == null) {
|
||||||
// maybe order "stop" here
|
// maybe order "stop" here
|
||||||
this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, CUnitAnimationListener.EMPTY, 1.0f);
|
this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void order(final COrder order, final boolean queue) {
|
public void order(final COrder order, final boolean queue) {
|
||||||
|
if (isDead()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (queue && (this.currentOrder != null)) {
|
if (queue && (this.currentOrder != null)) {
|
||||||
this.orderQueue.add(order);
|
this.orderQueue.add(order);
|
||||||
}
|
}
|
||||||
@ -225,13 +283,199 @@ public class CUnit extends CWidget {
|
|||||||
return this.defense;
|
return this.defense;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void damage(final CUnit source, final CAttackType attackType, final CWeaponType weaponType,
|
|
||||||
final int damage) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getImpactZ() {
|
public float getImpactZ() {
|
||||||
return this.unitType.getImpactZ();
|
return this.unitType.getImpactZ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double distance(final CWidget target) {
|
||||||
|
double dx = Math.abs(target.getX() - getX());
|
||||||
|
double dy = Math.abs(target.getY() - getY());
|
||||||
|
final float thisCollisionSize = this.unitType.getCollisionSize();
|
||||||
|
float targetCollisionSize;
|
||||||
|
if (target instanceof CUnit) {
|
||||||
|
final CUnitType targetUnitType = ((CUnit) target).getUnitType();
|
||||||
|
targetCollisionSize = targetUnitType.getCollisionSize();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
targetCollisionSize = 0; // TODO destructable collision size here
|
||||||
|
}
|
||||||
|
if (dx < 0) {
|
||||||
|
dx = 0;
|
||||||
|
}
|
||||||
|
if (dy < 0) {
|
||||||
|
dy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groundDistance = StrictMath.sqrt((dx * dx) + (dy * dy)) - thisCollisionSize - targetCollisionSize;
|
||||||
|
if (groundDistance < 0) {
|
||||||
|
groundDistance = 0;
|
||||||
|
}
|
||||||
|
return groundDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distance(final float x, final float y) {
|
||||||
|
double dx = Math.abs(x - getX());
|
||||||
|
double dy = Math.abs(y - getY());
|
||||||
|
final float thisCollisionSize = this.unitType.getCollisionSize();
|
||||||
|
if (dx < 0) {
|
||||||
|
dx = 0;
|
||||||
|
}
|
||||||
|
if (dy < 0) {
|
||||||
|
dy = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double groundDistance = StrictMath.sqrt((dx * dx) + (dy * dy)) - thisCollisionSize;
|
||||||
|
if (groundDistance < 0) {
|
||||||
|
groundDistance = 0;
|
||||||
|
}
|
||||||
|
return groundDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
|
||||||
|
final String weaponType, final float damage) {
|
||||||
|
final boolean wasDead = isDead();
|
||||||
|
final float damageRatioFromArmorClass = simulation.getGameplayConstants().getDamageRatioAgainst(attackType,
|
||||||
|
this.unitType.getDefenseType());
|
||||||
|
final float damageRatioFromDefense;
|
||||||
|
if (this.defense >= 0) {
|
||||||
|
damageRatioFromDefense = 1f - (float) (((this.defense) * 0.06) / (1 + (0.06 * this.defense)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -this.defense);
|
||||||
|
}
|
||||||
|
final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage;
|
||||||
|
this.life -= trueDamage;
|
||||||
|
simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType());
|
||||||
|
this.stateNotifier.lifeChanged();
|
||||||
|
if (!wasDead && isDead() && !this.unitType.isBuilding()) {
|
||||||
|
kill(simulation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void kill(final CSimulation simulation) {
|
||||||
|
this.currentOrder = null;
|
||||||
|
this.orderQueue.clear();
|
||||||
|
this.deathTurnTick = simulation.getGameTurnTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canReach(final CWidget target, final float range) {
|
||||||
|
final double distance = distance(target);
|
||||||
|
if (target instanceof CUnit) {
|
||||||
|
final CUnit targetUnit = (CUnit) target;
|
||||||
|
final CUnitType targetUnitType = targetUnit.getUnitType();
|
||||||
|
if (targetUnitType.isBuilding() && (targetUnitType.getBuildingPathingPixelMap() != null)) {
|
||||||
|
final float relativeOffsetX = getX() - target.getX();
|
||||||
|
final float relativeOffsetY = getY() - target.getY();
|
||||||
|
final int rotation = ((int) targetUnit.getFacing() + 450) % 360;
|
||||||
|
final BufferedImage buildingPathingPixelMap = targetUnitType.getBuildingPathingPixelMap();
|
||||||
|
final int gridWidth = ((rotation % 180) != 0) ? buildingPathingPixelMap.getHeight()
|
||||||
|
: buildingPathingPixelMap.getWidth();
|
||||||
|
final int gridHeight = ((rotation % 180) != 0) ? buildingPathingPixelMap.getWidth()
|
||||||
|
: buildingPathingPixelMap.getHeight();
|
||||||
|
final int relativeGridX = (int) Math.floor(relativeOffsetX / 32f) + (gridWidth / 2);
|
||||||
|
final int relativeGridY = (int) Math.floor(relativeOffsetY / 32f) + (gridHeight / 2);
|
||||||
|
final int rangeInCells = (int) Math.floor(range / 32f);
|
||||||
|
final int rangeInCellsSquare = rangeInCells * rangeInCells;
|
||||||
|
int minCheckX = relativeGridX - rangeInCells;
|
||||||
|
int minCheckY = relativeGridY - rangeInCells;
|
||||||
|
int maxCheckX = relativeGridX + rangeInCells;
|
||||||
|
int maxCheckY = relativeGridY + rangeInCells;
|
||||||
|
if ((minCheckX < gridWidth) && (maxCheckX >= 0)) {
|
||||||
|
if ((minCheckY < gridHeight) && (maxCheckY >= 0)) {
|
||||||
|
if (minCheckX < 0) {
|
||||||
|
minCheckX = 0;
|
||||||
|
}
|
||||||
|
if (minCheckY < 0) {
|
||||||
|
minCheckY = 0;
|
||||||
|
}
|
||||||
|
if (maxCheckX > (gridWidth - 1)) {
|
||||||
|
maxCheckX = gridWidth - 1;
|
||||||
|
}
|
||||||
|
if (maxCheckY > (gridHeight - 1)) {
|
||||||
|
maxCheckY = gridHeight - 1;
|
||||||
|
}
|
||||||
|
for (int checkX = minCheckX; checkX <= maxCheckX; checkX++) {
|
||||||
|
for (int checkY = minCheckY; checkY <= maxCheckY; checkY++) {
|
||||||
|
final int dx = relativeGridX - checkX;
|
||||||
|
final int dy = relativeGridY - checkY;
|
||||||
|
if (((dx * dx) + (dy * dy)) <= rangeInCellsSquare) {
|
||||||
|
if (((getRGBFromPixelData(buildingPathingPixelMap, checkX, checkY, rotation)
|
||||||
|
& 0xFF0000) >>> 16) > 127) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return distance <= range;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getRGBFromPixelData(final BufferedImage buildingPathingPixelMap, final int checkX, final int checkY,
|
||||||
|
final int rotation) {
|
||||||
|
|
||||||
|
// Below: y is downwards (:()
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
switch (rotation) {
|
||||||
|
case 90:
|
||||||
|
x = checkY;
|
||||||
|
y = buildingPathingPixelMap.getWidth() - 1 - checkX;
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
x = buildingPathingPixelMap.getWidth() - 1 - checkX;
|
||||||
|
y = buildingPathingPixelMap.getHeight() - 1 - checkY;
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
x = buildingPathingPixelMap.getHeight() - 1 - checkY;
|
||||||
|
y = checkX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 0:
|
||||||
|
x = checkX;
|
||||||
|
y = checkY;
|
||||||
|
}
|
||||||
|
return buildingPathingPixelMap.getRGB(x, buildingPathingPixelMap.getHeight() - 1 - y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addStateListener(final CUnitStateListener listener) {
|
||||||
|
this.stateNotifier.subscribe(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeStateListener(final CUnitStateListener listener) {
|
||||||
|
this.stateNotifier.unsubscribe(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCorpse() {
|
||||||
|
return this.corpse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBoneCorpse() {
|
||||||
|
return this.boneCorpse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source,
|
||||||
|
final EnumSet<CTargetType> targetsAllowed) {
|
||||||
|
if (targetsAllowed.containsAll(this.unitType.getTargetedAs())) {
|
||||||
|
final int sourcePlayerIndex = source.getPlayerIndex();
|
||||||
|
final CPlayer sourcePlayer = simulation.getPlayer(sourcePlayerIndex);
|
||||||
|
if (!targetsAllowed.contains(CTargetType.ENEMIES)
|
||||||
|
|| !sourcePlayer.hasAlliance(this.playerIndex, CAllianceType.PASSIVE)) {
|
||||||
|
if (isDead()) {
|
||||||
|
if (this.unitType.isRaise() && this.unitType.isDecay() && isBoneCorpse()) {
|
||||||
|
return targetsAllowed.contains(CTargetType.DEAD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return !targetsAllowed.contains(CTargetType.DEAD) || targetsAllowed.contains(CTargetType.ALIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
|
||||||
|
|
||||||
public interface CUnitAnimationListener {
|
public interface CUnitAnimationListener {
|
||||||
EnumSet<SecondaryTag> EMPTY = EnumSet.noneOf(SecondaryTag.class);
|
|
||||||
EnumSet<SecondaryTag> READY = EnumSet.of(SecondaryTag.READY);
|
|
||||||
|
|
||||||
void playAnimation(boolean force, final PrimaryTag animationName,
|
void playAnimation(boolean force, final PrimaryTag animationName,
|
||||||
final EnumSet<SecondaryTag> secondaryAnimationTags, float speedRatio);
|
final EnumSet<SecondaryTag> secondaryAnimationTags, float speedRatio, boolean allowRarityVariations);
|
||||||
|
|
||||||
void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags);
|
void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
|
||||||
|
boolean allowRarityVariations);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
public interface CUnitEnumFunction {
|
||||||
|
boolean call(CUnit unit);
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.util.SubscriberSetNotifier;
|
||||||
|
|
||||||
|
public interface CUnitStateListener {
|
||||||
|
void lifeChanged(); // hp (current) changes
|
||||||
|
|
||||||
|
public static final class CUnitStateNotifier extends SubscriberSetNotifier<CUnitStateListener>
|
||||||
|
implements CUnitStateListener {
|
||||||
|
@Override
|
||||||
|
public void lifeChanged() {
|
||||||
|
for (final CUnitStateListener listener : set) {
|
||||||
|
listener.lifeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,13 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
|
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.environment.PathingGrid.MovementType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,11 +27,18 @@ public class CUnitType {
|
|||||||
private final boolean decay;
|
private final boolean decay;
|
||||||
private final CDefenseType defenseType;
|
private final CDefenseType defenseType;
|
||||||
private final float impactZ;
|
private final float impactZ;
|
||||||
|
private final float deathTime;
|
||||||
|
|
||||||
|
// TODO: this should probably not be stored as game state, i.e., is it really
|
||||||
|
// game data? can we store it in a cleaner way?
|
||||||
|
private final BufferedImage buildingPathingPixelMap;
|
||||||
|
private final EnumSet<CTargetType> targetedAs;
|
||||||
|
|
||||||
public CUnitType(final String name, final boolean isBldg, final MovementType movementType,
|
public CUnitType(final String name, final boolean isBldg, final MovementType movementType,
|
||||||
final float defaultFlyingHeight, final float collisionSize,
|
final float defaultFlyingHeight, final float collisionSize,
|
||||||
final EnumSet<CUnitClassification> classifications, final List<CUnitAttack> attacks, final String armorType,
|
final EnumSet<CUnitClassification> classifications, final List<CUnitAttack> attacks, final String armorType,
|
||||||
final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ) {
|
final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ,
|
||||||
|
final BufferedImage buildingPathingPixelMap, final float deathTime, final EnumSet<CTargetType> targetedAs) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.building = isBldg;
|
this.building = isBldg;
|
||||||
this.movementType = movementType;
|
this.movementType = movementType;
|
||||||
@ -42,6 +51,9 @@ public class CUnitType {
|
|||||||
this.decay = decay;
|
this.decay = decay;
|
||||||
this.defenseType = defenseType;
|
this.defenseType = defenseType;
|
||||||
this.impactZ = impactZ;
|
this.impactZ = impactZ;
|
||||||
|
this.buildingPathingPixelMap = buildingPathingPixelMap;
|
||||||
|
this.deathTime = deathTime;
|
||||||
|
this.targetedAs = targetedAs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -91,4 +103,16 @@ public class CUnitType {
|
|||||||
public float getImpactZ() {
|
public float getImpactZ() {
|
||||||
return this.impactZ;
|
return this.impactZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferedImage getBuildingPathingPixelMap() {
|
||||||
|
return this.buildingPathingPixelMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDeathTime() {
|
||||||
|
return this.deathTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnumSet<CTargetType> getTargetedAs() {
|
||||||
|
return this.targetedAs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
|
|
||||||
public abstract class CWidget {
|
public abstract class CWidget {
|
||||||
private final int handleId;
|
private final int handleId;
|
||||||
private float x;
|
private float x;
|
||||||
private float y;
|
private float y;
|
||||||
private float life;
|
protected float life;
|
||||||
|
|
||||||
public CWidget(final int handleId, final float x, final float y, final float life) {
|
public CWidget(final int handleId, final float x, final float y, final float life) {
|
||||||
this.handleId = handleId;
|
this.handleId = handleId;
|
||||||
@ -41,12 +46,17 @@ public abstract class CWidget {
|
|||||||
this.life = life;
|
this.life = life;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void damage(final CUnit source, final int damage) {
|
public abstract void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
|
||||||
this.life -= damage;
|
final String weaponType, final float damage);
|
||||||
}
|
|
||||||
|
|
||||||
public abstract float getFlyHeight();
|
public abstract float getFlyHeight();
|
||||||
|
|
||||||
public abstract float getImpactZ();
|
public abstract float getImpactZ();
|
||||||
|
|
||||||
|
public boolean isDead() {
|
||||||
|
return this.life <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean canBeTargetedBy(CSimulation simulation, CUnit source,
|
||||||
|
final EnumSet<CTargetType> targetsAllowed);
|
||||||
}
|
}
|
||||||
|
@ -1,77 +1,145 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Rectangle;
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
import com.etheller.warsmash.util.Quadtree;
|
import com.etheller.warsmash.util.Quadtree;
|
||||||
|
import com.etheller.warsmash.util.QuadtreeIntersector;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
|
||||||
|
|
||||||
public class CWorldCollision {
|
public class CWorldCollision {
|
||||||
private final Quadtree<CUnit> groundUnitCollision;
|
private final Quadtree<CUnit> groundUnitCollision;
|
||||||
private final Quadtree<CUnit> airUnitCollision;
|
private final Quadtree<CUnit> airUnitCollision;
|
||||||
private final Quadtree<CUnit> seaUnitCollision;
|
private final Quadtree<CUnit> seaUnitCollision;
|
||||||
|
private final Quadtree<CUnit> buildingUnitCollision;
|
||||||
|
private final float maxCollisionRadius;
|
||||||
|
private final AnyUnitExceptTwoIntersector anyUnitExceptTwoIntersector;
|
||||||
|
private final EachUnitOnlyOnceIntersector eachUnitOnlyOnceIntersector;
|
||||||
|
|
||||||
public CWorldCollision(final Rectangle entireMapBounds) {
|
public CWorldCollision(final Rectangle entireMapBounds, final float maxCollisionRadius) {
|
||||||
this.groundUnitCollision = new Quadtree<>(entireMapBounds);
|
this.groundUnitCollision = new Quadtree<>(entireMapBounds);
|
||||||
this.airUnitCollision = new Quadtree<>(entireMapBounds);
|
this.airUnitCollision = new Quadtree<>(entireMapBounds);
|
||||||
this.seaUnitCollision = new Quadtree<>(entireMapBounds);
|
this.seaUnitCollision = new Quadtree<>(entireMapBounds);
|
||||||
|
this.buildingUnitCollision = new Quadtree<>(entireMapBounds);
|
||||||
|
this.maxCollisionRadius = maxCollisionRadius;
|
||||||
|
this.anyUnitExceptTwoIntersector = new AnyUnitExceptTwoIntersector();
|
||||||
|
this.eachUnitOnlyOnceIntersector = new EachUnitOnlyOnceIntersector();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addUnit(final CUnit unit) {
|
public void addUnit(final CUnit unit) {
|
||||||
if (unit.getUnitType().isBuilding()) {
|
|
||||||
throw new IllegalArgumentException("Cannot add building to the CWorldCollision");
|
|
||||||
}
|
|
||||||
Rectangle bounds = unit.getCollisionRectangle();
|
Rectangle bounds = unit.getCollisionRectangle();
|
||||||
if (bounds == null) {
|
if (bounds == null) {
|
||||||
final float collisionSize = unit.getUnitType().getCollisionSize();
|
final float collisionSize = Math.min(this.maxCollisionRadius, unit.getUnitType().getCollisionSize());
|
||||||
bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2,
|
bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2,
|
||||||
collisionSize * 2);
|
collisionSize * 2);
|
||||||
unit.setCollisionRectangle(bounds);
|
unit.setCollisionRectangle(bounds);
|
||||||
}
|
}
|
||||||
final MovementType movementType = unit.getUnitType().getMovementType();
|
if (unit.getUnitType().isBuilding()) {
|
||||||
if (movementType != null) {
|
// buildings are here so that we can include them when enumerating all units in
|
||||||
switch (movementType) {
|
// a rect, but they don't really move dynamically, this is kind of pointless
|
||||||
case AMPHIBIOUS:
|
this.buildingUnitCollision.add(unit, bounds);
|
||||||
this.seaUnitCollision.add(unit, bounds);
|
}
|
||||||
this.groundUnitCollision.add(unit, bounds);
|
else {
|
||||||
break;
|
final MovementType movementType = unit.getUnitType().getMovementType();
|
||||||
case FLOAT:
|
if (movementType != null) {
|
||||||
this.seaUnitCollision.add(unit, bounds);
|
switch (movementType) {
|
||||||
break;
|
case AMPHIBIOUS:
|
||||||
case FLY:
|
this.seaUnitCollision.add(unit, bounds);
|
||||||
this.airUnitCollision.add(unit, bounds);
|
this.groundUnitCollision.add(unit, bounds);
|
||||||
break;
|
break;
|
||||||
default:
|
case FLOAT:
|
||||||
case DISABLED:
|
this.seaUnitCollision.add(unit, bounds);
|
||||||
case FOOT:
|
break;
|
||||||
case HORSE:
|
case FLY:
|
||||||
case HOVER:
|
this.airUnitCollision.add(unit, bounds);
|
||||||
this.groundUnitCollision.add(unit, bounds);
|
break;
|
||||||
break;
|
default:
|
||||||
|
case DISABLED:
|
||||||
|
case FOOT:
|
||||||
|
case HORSE:
|
||||||
|
case HOVER:
|
||||||
|
this.groundUnitCollision.add(unit, bounds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeUnit(final CUnit unit) {
|
||||||
|
final Rectangle bounds = unit.getCollisionRectangle();
|
||||||
|
if (bounds != null) {
|
||||||
|
if (unit.getUnitType().isBuilding()) {
|
||||||
|
this.buildingUnitCollision.remove(unit, bounds);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final MovementType movementType = unit.getUnitType().getMovementType();
|
||||||
|
if (movementType != null) {
|
||||||
|
switch (movementType) {
|
||||||
|
case AMPHIBIOUS:
|
||||||
|
this.seaUnitCollision.remove(unit, bounds);
|
||||||
|
this.groundUnitCollision.remove(unit, bounds);
|
||||||
|
break;
|
||||||
|
case FLOAT:
|
||||||
|
this.seaUnitCollision.remove(unit, bounds);
|
||||||
|
break;
|
||||||
|
case FLY:
|
||||||
|
this.airUnitCollision.remove(unit, bounds);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case DISABLED:
|
||||||
|
case FOOT:
|
||||||
|
case HORSE:
|
||||||
|
case HOVER:
|
||||||
|
this.groundUnitCollision.remove(unit, bounds);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unit.setCollisionRectangle(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enumUnitsInRect(final Rectangle rect, final CUnitEnumFunction callback) {
|
||||||
|
this.eachUnitOnlyOnceIntersector.reset(callback);
|
||||||
|
this.groundUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector);
|
||||||
|
this.airUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector);
|
||||||
|
this.seaUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector);
|
||||||
|
this.buildingUnitCollision.intersect(rect, this.eachUnitOnlyOnceIntersector);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore,
|
public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore,
|
||||||
final MovementType movementType) {
|
final MovementType movementType) {
|
||||||
|
return intersectsAnythingOtherThan(newPossibleRectangle, sourceUnitToIgnore, null, movementType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersectsAnythingOtherThan(final Rectangle newPossibleRectangle, final CUnit sourceUnitToIgnore,
|
||||||
|
final CUnit sourceSecondUnitToIgnore, final MovementType movementType) {
|
||||||
if (movementType != null) {
|
if (movementType != null) {
|
||||||
switch (movementType) {
|
switch (movementType) {
|
||||||
case AMPHIBIOUS:
|
case AMPHIBIOUS:
|
||||||
if (this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) {
|
if (this.seaUnitCollision.intersect(newPossibleRectangle,
|
||||||
|
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) {
|
if (this.groundUnitCollision.intersect(newPossibleRectangle,
|
||||||
|
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case FLOAT:
|
case FLOAT:
|
||||||
return this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
|
return this.seaUnitCollision.intersect(newPossibleRectangle,
|
||||||
|
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
|
||||||
case FLY:
|
case FLY:
|
||||||
return this.airUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
|
return this.airUnitCollision.intersect(newPossibleRectangle,
|
||||||
|
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
|
||||||
default:
|
default:
|
||||||
case DISABLED:
|
case DISABLED:
|
||||||
case FOOT:
|
case FOOT:
|
||||||
case HORSE:
|
case HORSE:
|
||||||
case HOVER:
|
case HOVER:
|
||||||
return this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
|
return this.groundUnitCollision.intersect(newPossibleRectangle,
|
||||||
|
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -109,4 +177,45 @@ public class CWorldCollision {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class AnyUnitExceptTwoIntersector implements QuadtreeIntersector<CUnit> {
|
||||||
|
private CUnit firstUnit;
|
||||||
|
private CUnit secondUnit;
|
||||||
|
|
||||||
|
public AnyUnitExceptTwoIntersector reset(final CUnit firstUnit, final CUnit secondUnit) {
|
||||||
|
this.firstUnit = firstUnit;
|
||||||
|
this.secondUnit = secondUnit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onIntersect(final CUnit intersectingObject) {
|
||||||
|
return (intersectingObject != this.firstUnit) && (intersectingObject != this.secondUnit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class EachUnitOnlyOnceIntersector implements QuadtreeIntersector<CUnit> {
|
||||||
|
private CUnitEnumFunction consumerDelegate;
|
||||||
|
private final Set<CUnit> intersectedUnits = new HashSet<>();
|
||||||
|
private boolean done;
|
||||||
|
|
||||||
|
public EachUnitOnlyOnceIntersector reset(final CUnitEnumFunction consumerDelegate) {
|
||||||
|
this.consumerDelegate = consumerDelegate;
|
||||||
|
this.intersectedUnits.clear();
|
||||||
|
this.done = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onIntersect(final CUnit intersectingObject) {
|
||||||
|
if (this.done) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.intersectedUnits.add(intersectingObject)) {
|
||||||
|
this.done = this.consumerDelegate.call(intersectingObject);
|
||||||
|
return this.done;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities;
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2;
|
import com.badlogic.gdx.math.Vector2;
|
||||||
|
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.CSimulation;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
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.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CAttackOrder;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.CMoveOrder;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType;
|
||||||
@ -47,7 +50,17 @@ public class CAbilityAttack implements CAbility {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) {
|
public void onOrder(final CSimulation game, final CUnit caster, final CWidget target, final boolean queue) {
|
||||||
caster.order(new CAttackOrder(caster, caster.getUnitType().getAttacks().get(0), target), queue);
|
COrder order = null;
|
||||||
|
for (final CUnitAttack attack : caster.getUnitType().getAttacks()) {
|
||||||
|
if (target.canBeTargetedBy(game, caster, attack.getTargetsAllowed())) {
|
||||||
|
order = new CAttackOrder(caster, attack, target);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (order == null) {
|
||||||
|
order = new CMoveOrder(caster, target.getX(), target.getY());
|
||||||
|
}
|
||||||
|
caster.order(order, queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -10,14 +10,21 @@ public enum CAttackType implements CodeKeyType {
|
|||||||
MAGIC,
|
MAGIC,
|
||||||
HERO;
|
HERO;
|
||||||
|
|
||||||
|
public static CAttackType[] VALUES = values();
|
||||||
|
|
||||||
private String codeKey;
|
private String codeKey;
|
||||||
|
private String damageKey;
|
||||||
|
|
||||||
private CAttackType() {
|
private CAttackType() {
|
||||||
String name = name();
|
final String name = name();
|
||||||
if (name.equals("SPELLS")) {
|
final String computedCodeKey = name.charAt(0) + name.substring(1).toLowerCase();
|
||||||
name = "MAGIC";
|
if (computedCodeKey.equals("Spells")) {
|
||||||
|
this.codeKey = "Magic";
|
||||||
}
|
}
|
||||||
this.codeKey = name.charAt(0) + name.substring(1).toLowerCase();
|
else {
|
||||||
|
this.codeKey = computedCodeKey;
|
||||||
|
}
|
||||||
|
this.damageKey = this.codeKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -25,6 +32,10 @@ public enum CAttackType implements CodeKeyType {
|
|||||||
return this.codeKey;
|
return this.codeKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDamageKey() {
|
||||||
|
return this.damageKey;
|
||||||
|
}
|
||||||
|
|
||||||
public static CAttackType parseAttackType(final String attackTypeString) {
|
public static CAttackType parseAttackType(final String attackTypeString) {
|
||||||
return valueOf(attackTypeString.toUpperCase());
|
return valueOf(attackTypeString.toUpperCase());
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ public enum CDefenseType implements CodeKeyType {
|
|||||||
HERO,
|
HERO,
|
||||||
DIVINE;
|
DIVINE;
|
||||||
|
|
||||||
|
public static CDefenseType[] VALUES = values();
|
||||||
|
|
||||||
private String codeKey;
|
private String codeKey;
|
||||||
|
|
||||||
private CDefenseType() {
|
private CDefenseType() {
|
||||||
|
@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -19,7 +22,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
|||||||
* because many of those settings did not exist. So I will attempt to emulate
|
* because many of those settings did not exist. So I will attempt to emulate
|
||||||
* these attacks as best as possible.
|
* these attacks as best as possible.
|
||||||
*/
|
*/
|
||||||
public class CUnitAttack {
|
public abstract class CUnitAttack {
|
||||||
private float animationBackswingPoint;
|
private float animationBackswingPoint;
|
||||||
private float animationDamagePoint;
|
private float animationDamagePoint;
|
||||||
private CAttackType attackType;
|
private CAttackType attackType;
|
||||||
@ -195,4 +198,5 @@ public class CUnitAttack {
|
|||||||
return this.maxDamage;
|
return this.maxDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract void launch(CSimulation simulation, CUnit unit, CWidget target, float damage);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -28,4 +31,10 @@ public class CUnitAttackInstant extends CUnitAttack {
|
|||||||
this.projectileArt = projectileArt;
|
this.projectileArt = projectileArt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) {
|
||||||
|
simulation.createInstantAttackEffect(unit, this, target);
|
||||||
|
target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -59,4 +62,14 @@ public class CUnitAttackMissile extends CUnitAttack {
|
|||||||
this.projectileSpeed = projectileSpeed;
|
this.projectileSpeed = projectileSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) {
|
||||||
|
simulation.createProjectile(unit, unit.getX(), unit.getY(), (float) Math.toRadians(unit.getFacing()), this,
|
||||||
|
target, damage, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage,
|
||||||
|
final float x, final float y, final int bounceIndex) {
|
||||||
|
target.damage(cSimulation, source, getAttackType(), getWeaponSound(), damage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -9,6 +14,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
|||||||
public class CUnitAttackMissileBounce extends CUnitAttackMissile {
|
public class CUnitAttackMissileBounce extends CUnitAttackMissile {
|
||||||
private float damageLossFactor;
|
private float damageLossFactor;
|
||||||
private int maximumNumberOfTargets;
|
private int maximumNumberOfTargets;
|
||||||
|
private final int areaOfEffectFullDamage;
|
||||||
|
private final EnumSet<CTargetType> areaOfEffectTargets;
|
||||||
|
|
||||||
public CUnitAttackMissileBounce(final float animationBackswingPoint, final float animationDamagePoint,
|
public CUnitAttackMissileBounce(final float animationBackswingPoint, final float animationDamagePoint,
|
||||||
final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice,
|
final CAttackType attackType, final float cooldownTime, final int damageBase, final int damageDice,
|
||||||
@ -16,12 +23,15 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile {
|
|||||||
final boolean showUI, final EnumSet<CTargetType> targetsAllowed, final String weaponSound,
|
final boolean showUI, final EnumSet<CTargetType> targetsAllowed, final String weaponSound,
|
||||||
final CWeaponType weaponType, final float projectileArc, final String projectileArt,
|
final CWeaponType weaponType, final float projectileArc, final String projectileArt,
|
||||||
final boolean projectileHomingEnabled, final int projectileSpeed, final float damageLossFactor,
|
final boolean projectileHomingEnabled, final int projectileSpeed, final float damageLossFactor,
|
||||||
final int maximumNumberOfTargets) {
|
final int maximumNumberOfTargets, final int areaOfEffectFullDamage,
|
||||||
|
final EnumSet<CTargetType> areaOfEffectTargets) {
|
||||||
super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice,
|
super(animationBackswingPoint, animationDamagePoint, attackType, cooldownTime, damageBase, damageDice,
|
||||||
damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound,
|
damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound,
|
||||||
weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed);
|
weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed);
|
||||||
this.damageLossFactor = damageLossFactor;
|
this.damageLossFactor = damageLossFactor;
|
||||||
this.maximumNumberOfTargets = maximumNumberOfTargets;
|
this.maximumNumberOfTargets = maximumNumberOfTargets;
|
||||||
|
this.areaOfEffectFullDamage = areaOfEffectFullDamage;
|
||||||
|
this.areaOfEffectTargets = areaOfEffectTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getDamageLossFactor() {
|
public float getDamageLossFactor() {
|
||||||
@ -40,4 +50,67 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile {
|
|||||||
this.maximumNumberOfTargets = maximumNumberOfTargets;
|
this.maximumNumberOfTargets = maximumNumberOfTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage,
|
||||||
|
final float x, final float y, final int bounceIndex) {
|
||||||
|
super.doDamage(cSimulation, source, target, damage, x, y, bounceIndex);
|
||||||
|
final int nextBounceIndex = bounceIndex + 1;
|
||||||
|
if (nextBounceIndex != this.maximumNumberOfTargets) {
|
||||||
|
BounceMissileConsumer.INSTANCE.nextBounce(cSimulation, source, target, this, x, y, damage, nextBounceIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BounceMissileConsumer implements CUnitEnumFunction {
|
||||||
|
private static final BounceMissileConsumer INSTANCE = new BounceMissileConsumer();
|
||||||
|
private final Rectangle rect = new Rectangle();
|
||||||
|
private CUnitAttackMissileBounce attack;
|
||||||
|
private CSimulation simulation;
|
||||||
|
private CUnit source;
|
||||||
|
private CWidget target;
|
||||||
|
private float x;
|
||||||
|
private float y;
|
||||||
|
private float damage;
|
||||||
|
private int bounceIndex;
|
||||||
|
private boolean launched = false;
|
||||||
|
|
||||||
|
public void nextBounce(final CSimulation simulation, final CUnit source, final CWidget target,
|
||||||
|
final CUnitAttackMissileBounce attack, final float x, final float y, final float damage,
|
||||||
|
final int bounceIndex) {
|
||||||
|
this.simulation = simulation;
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
this.attack = attack;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.damage = damage;
|
||||||
|
this.bounceIndex = bounceIndex;
|
||||||
|
this.launched = false;
|
||||||
|
final float doubleMaxArea = attack.areaOfEffectFullDamage
|
||||||
|
+ (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2);
|
||||||
|
final float maxArea = doubleMaxArea / 2;
|
||||||
|
this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea);
|
||||||
|
simulation.getWorldCollision().enumUnitsInRect(this.rect, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean call(final CUnit enumUnit) {
|
||||||
|
if (enumUnit == this.target) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) {
|
||||||
|
if (this.launched) {
|
||||||
|
throw new IllegalStateException("already launched");
|
||||||
|
}
|
||||||
|
final float dx = enumUnit.getX() - this.x;
|
||||||
|
final float dy = enumUnit.getY() - this.y;
|
||||||
|
final float angle = (float) Math.atan2(dy, dx);
|
||||||
|
this.simulation.createProjectile(this.source, this.x, this.y, angle, this.attack, enumUnit,
|
||||||
|
this.damage * (1.0f - this.attack.damageLossFactor), this.bounceIndex);
|
||||||
|
this.launched = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitEnumFunction;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -82,4 +87,66 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile {
|
|||||||
this.damageFactorSmall = damageFactorSmall;
|
this.damageFactorSmall = damageFactorSmall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doDamage(final CSimulation cSimulation, final CUnit source, final CWidget target, final float damage,
|
||||||
|
final float x, final float y, final int bounceIndex) {
|
||||||
|
SplashDamageConsumer.INSTANCE.doDamage(cSimulation, source, target, this, x, y, damage);
|
||||||
|
if ((getWeaponType() != CWeaponType.ARTILLERY) && !SplashDamageConsumer.INSTANCE.hitTarget) {
|
||||||
|
super.doDamage(cSimulation, source, target, damage * this.damageFactorSmall, x, y, bounceIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SplashDamageConsumer implements CUnitEnumFunction {
|
||||||
|
private static final SplashDamageConsumer INSTANCE = new SplashDamageConsumer();
|
||||||
|
private final Rectangle rect = new Rectangle();
|
||||||
|
private CUnitAttackMissileSplash attack;
|
||||||
|
private CSimulation simulation;
|
||||||
|
private CUnit source;
|
||||||
|
private CWidget target;
|
||||||
|
private float x;
|
||||||
|
private float y;
|
||||||
|
private float damage;
|
||||||
|
private boolean hitTarget;
|
||||||
|
|
||||||
|
public void doDamage(final CSimulation simulation, final CUnit source, final CWidget target,
|
||||||
|
final CUnitAttackMissileSplash attack, final float x, final float y, final float damage) {
|
||||||
|
this.simulation = simulation;
|
||||||
|
this.source = source;
|
||||||
|
this.target = target;
|
||||||
|
this.attack = attack;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.damage = damage;
|
||||||
|
this.hitTarget = false;
|
||||||
|
final float doubleMaxArea = attack.areaOfEffectSmallDamage
|
||||||
|
+ (this.simulation.getGameplayConstants().getCloseEnoughRange() * 2);
|
||||||
|
final float maxArea = doubleMaxArea / 2;
|
||||||
|
this.rect.set(x - maxArea, y - maxArea, doubleMaxArea, doubleMaxArea);
|
||||||
|
simulation.getWorldCollision().enumUnitsInRect(this.rect, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean call(final CUnit enumUnit) {
|
||||||
|
if (enumUnit.canBeTargetedBy(this.simulation, this.source, this.attack.areaOfEffectTargets)) {
|
||||||
|
final double distance = enumUnit.distance(this.x, this.y)
|
||||||
|
- this.simulation.getGameplayConstants().getCloseEnoughRange();
|
||||||
|
if (distance <= (this.attack.areaOfEffectFullDamage / 2)) {
|
||||||
|
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
|
||||||
|
this.attack.getWeaponSound(), this.damage);
|
||||||
|
}
|
||||||
|
else if (distance <= (this.attack.areaOfEffectMediumDamage / 2)) {
|
||||||
|
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
|
||||||
|
this.attack.getWeaponSound(), this.damage * this.attack.damageFactorMedium);
|
||||||
|
}
|
||||||
|
else if (distance <= (this.attack.areaOfEffectSmallDamage / 2)) {
|
||||||
|
enumUnit.damage(this.simulation, this.source, this.attack.getAttackType(),
|
||||||
|
this.attack.getWeaponSound(), this.damage * this.attack.damageFactorSmall);
|
||||||
|
}
|
||||||
|
if (enumUnit == this.target) {
|
||||||
|
this.hitTarget = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
|
|||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
@ -18,4 +21,9 @@ public class CUnitAttackNormal extends CUnitAttack {
|
|||||||
weaponType);
|
weaponType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) {
|
||||||
|
target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,29 +4,39 @@ import com.etheller.warsmash.util.WarsmashConstants;
|
|||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
|
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.CUnit;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
|
||||||
|
|
||||||
public class CAttackProjectile {
|
public class CAttackProjectile {
|
||||||
private float x;
|
private float x;
|
||||||
private float y;
|
private float y;
|
||||||
|
private final float initialTargetX;
|
||||||
|
private final float initialTargetY;
|
||||||
private final float speed;
|
private final float speed;
|
||||||
private final CWidget target;
|
private final CWidget target;
|
||||||
private boolean done;
|
private boolean done;
|
||||||
private final CUnit source;
|
private final CUnit source;
|
||||||
private final int damage;
|
private final float damage;
|
||||||
|
private final CUnitAttackMissile unitAttack;
|
||||||
|
private final int bounceIndex;
|
||||||
|
|
||||||
public CAttackProjectile(final float x, final float y, final float speed, final CWidget target, final CUnit source,
|
public CAttackProjectile(final float x, final float y, final float speed, final CWidget target, final CUnit source,
|
||||||
final int damage) {
|
final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
this.unitAttack = unitAttack;
|
||||||
|
this.bounceIndex = bounceIndex;
|
||||||
|
this.initialTargetX = target.getX();
|
||||||
|
this.initialTargetY = target.getY();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean update(final CSimulation cSimulation) {
|
public boolean update(final CSimulation cSimulation) {
|
||||||
final float tx = this.target.getX();
|
final float tx = getTargetX();
|
||||||
final float ty = this.target.getY();
|
final float ty = getTargetY();
|
||||||
final float sx = this.x;
|
final float sx = this.x;
|
||||||
final float sy = this.y;
|
final float sy = this.y;
|
||||||
final float dtsx = tx - sx;
|
final float dtsx = tx - sx;
|
||||||
@ -39,7 +49,8 @@ public class CAttackProjectile {
|
|||||||
float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME);
|
float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME);
|
||||||
if (c <= travelDistance) {
|
if (c <= travelDistance) {
|
||||||
if (!this.done) {
|
if (!this.done) {
|
||||||
this.target.damage(this.source, this.damage);
|
this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y,
|
||||||
|
this.bounceIndex);
|
||||||
}
|
}
|
||||||
this.done = true;
|
this.done = true;
|
||||||
travelDistance = c;
|
travelDistance = c;
|
||||||
@ -73,4 +84,26 @@ public class CAttackProjectile {
|
|||||||
public boolean isDone() {
|
public boolean isDone() {
|
||||||
return this.done;
|
return this.done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CUnitAttackMissile getUnitAttack() {
|
||||||
|
return this.unitAttack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTargetX() {
|
||||||
|
if (this.unitAttack.isProjectileHomingEnabled() && (this.unitAttack.getWeaponType() != CWeaponType.ARTILLERY)) {
|
||||||
|
return this.target.getX();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.initialTargetX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getTargetY() {
|
||||||
|
if (this.unitAttack.isProjectileHomingEnabled() && (this.unitAttack.getWeaponType() != CWeaponType.ARTILLERY)) {
|
||||||
|
return this.target.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.initialTargetY;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -113,6 +114,8 @@ public class CUnitData {
|
|||||||
private static final War3ID MOVE_TYPE = War3ID.fromString("umvt");
|
private static final War3ID MOVE_TYPE = War3ID.fromString("umvt");
|
||||||
private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol");
|
private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol");
|
||||||
private static final War3ID CLASSIFICATION = War3ID.fromString("utyp");
|
private static final War3ID CLASSIFICATION = War3ID.fromString("utyp");
|
||||||
|
private static final War3ID DEATH_TIME = War3ID.fromString("udtm");
|
||||||
|
private static final War3ID TARGETED_AS = War3ID.fromString("utar");
|
||||||
private final MutableObjectData unitData;
|
private final MutableObjectData unitData;
|
||||||
private final Map<War3ID, CUnitType> unitIdToUnitType = new HashMap<>();
|
private final Map<War3ID, CUnitType> unitIdToUnitType = new HashMap<>();
|
||||||
|
|
||||||
@ -121,124 +124,137 @@ public class CUnitData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public CUnit create(final CSimulation simulation, final int playerIndex, final int handleId, final War3ID typeId,
|
public CUnit create(final CSimulation simulation, final int playerIndex, final int handleId, final War3ID typeId,
|
||||||
final float x, final float y, final float facing) {
|
final float x, final float y, final float facing, final BufferedImage buildingPathingPixelMap) {
|
||||||
final MutableGameObject unitType = this.unitData.get(typeId);
|
final MutableGameObject unitType = this.unitData.get(typeId);
|
||||||
final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0);
|
final int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0);
|
||||||
final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0);
|
final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0);
|
||||||
final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0);
|
final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0);
|
||||||
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
|
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
|
||||||
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
|
final int defense = unitType.getFieldAsInteger(DEFENSE, 0);
|
||||||
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
|
|
||||||
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
|
CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId);
|
||||||
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
|
if (unitTypeInstance == null) {
|
||||||
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
|
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
|
||||||
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
|
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
|
||||||
final String unitName = unitType.getFieldAsString(NAME, 0);
|
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
|
||||||
final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
|
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
|
||||||
if (classificationString != null) {
|
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
|
||||||
final String[] classificationValues = classificationString.split(",");
|
final String unitName = unitType.getFieldAsString(NAME, 0);
|
||||||
for (final String unitEditorKey : classificationValues) {
|
final EnumSet<CTargetType> targetedAs = CTargetType
|
||||||
final CUnitClassification unitClassification = CUnitClassification
|
.parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0));
|
||||||
.parseUnitClassification(unitEditorKey);
|
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
|
||||||
if (unitClassification != null) {
|
final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
|
||||||
classifications.add(unitClassification);
|
if (classificationString != null) {
|
||||||
|
final String[] classificationValues = classificationString.split(",");
|
||||||
|
for (final String unitEditorKey : classificationValues) {
|
||||||
|
final CUnitClassification unitClassification = CUnitClassification
|
||||||
|
.parseUnitClassification(unitEditorKey);
|
||||||
|
if (unitClassification != null) {
|
||||||
|
classifications.add(unitClassification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
final List<CUnitAttack> attacks = new ArrayList<>();
|
||||||
|
final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0);
|
||||||
|
if ((attacksEnabled & 0x1) != 0) {
|
||||||
|
// attack one
|
||||||
|
final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0);
|
||||||
|
final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0);
|
||||||
|
final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0);
|
||||||
|
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0);
|
||||||
|
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0);
|
||||||
|
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
|
||||||
|
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0));
|
||||||
|
final CAttackType attackType = CAttackType
|
||||||
|
.parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0));
|
||||||
|
final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0);
|
||||||
|
final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0);
|
||||||
|
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0);
|
||||||
|
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0);
|
||||||
|
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0);
|
||||||
|
final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0);
|
||||||
|
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0);
|
||||||
|
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0);
|
||||||
|
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0);
|
||||||
|
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0);
|
||||||
|
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0);
|
||||||
|
final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0);
|
||||||
|
final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0);
|
||||||
|
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED,
|
||||||
|
0);
|
||||||
|
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0);
|
||||||
|
final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0);
|
||||||
|
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0);
|
||||||
|
final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0);
|
||||||
|
final EnumSet<CTargetType> targetsAllowed = CTargetType
|
||||||
|
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0));
|
||||||
|
final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0);
|
||||||
|
final CWeaponType weaponType = CWeaponType
|
||||||
|
.parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0));
|
||||||
|
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
|
||||||
|
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType,
|
||||||
|
cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice,
|
||||||
|
damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount,
|
||||||
|
maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed,
|
||||||
|
range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType));
|
||||||
|
}
|
||||||
|
if ((attacksEnabled & 0x2) != 0) {
|
||||||
|
// attack two
|
||||||
|
final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0);
|
||||||
|
final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0);
|
||||||
|
final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0);
|
||||||
|
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0);
|
||||||
|
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0);
|
||||||
|
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
|
||||||
|
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0));
|
||||||
|
final CAttackType attackType = CAttackType
|
||||||
|
.parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0));
|
||||||
|
final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0);
|
||||||
|
final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0);
|
||||||
|
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0);
|
||||||
|
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0);
|
||||||
|
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0);
|
||||||
|
final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0);
|
||||||
|
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0);
|
||||||
|
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0);
|
||||||
|
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0);
|
||||||
|
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0);
|
||||||
|
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0);
|
||||||
|
final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0);
|
||||||
|
final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0);
|
||||||
|
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED,
|
||||||
|
0);
|
||||||
|
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0);
|
||||||
|
final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0);
|
||||||
|
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0);
|
||||||
|
final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0);
|
||||||
|
final EnumSet<CTargetType> targetsAllowed = CTargetType
|
||||||
|
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0));
|
||||||
|
final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0);
|
||||||
|
final CWeaponType weaponType = CWeaponType
|
||||||
|
.parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0));
|
||||||
|
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
|
||||||
|
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType,
|
||||||
|
cooldownTime, damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice,
|
||||||
|
damageSidesPerDie, damageSpillDistance, damageSpillRadius, damageUpgradeAmount,
|
||||||
|
maximumNumberOfTargets, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed,
|
||||||
|
range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType));
|
||||||
|
}
|
||||||
|
final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0);
|
||||||
|
final boolean raise = (deathType & 0x1) != 0;
|
||||||
|
final boolean decay = (deathType & 0x2) != 0;
|
||||||
|
final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0);
|
||||||
|
final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0);
|
||||||
|
final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0));
|
||||||
|
final float deathTime = unitType.getFieldAsFloat(DEATH_TIME, 0);
|
||||||
|
unitTypeInstance = new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize, classifications,
|
||||||
|
attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, deathTime,
|
||||||
|
targetedAs);
|
||||||
|
this.unitIdToUnitType.put(typeId, unitTypeInstance);
|
||||||
}
|
}
|
||||||
final List<CUnitAttack> attacks = new ArrayList<>();
|
|
||||||
final int attacksEnabled = unitType.getFieldAsInteger(ATTACKS_ENABLED, 0);
|
|
||||||
if ((attacksEnabled & 0x1) != 0) {
|
|
||||||
// attack one
|
|
||||||
final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK1_BACKSWING_POINT, 0);
|
|
||||||
final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK1_DAMAGE_POINT, 0);
|
|
||||||
final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_FULL_DMG, 0);
|
|
||||||
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_HALF_DMG, 0);
|
|
||||||
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK1_AREA_OF_EFFECT_QUARTER_DMG, 0);
|
|
||||||
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
|
|
||||||
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_AREA_OF_EFFECT_TARGETS, 0));
|
|
||||||
final CAttackType attackType = CAttackType
|
|
||||||
.parseAttackType(unitType.getFieldAsString(ATTACK1_ATTACK_TYPE, 0));
|
|
||||||
final float cooldownTime = unitType.getFieldAsFloat(ATTACK1_COOLDOWN, 0);
|
|
||||||
final int damageBase = unitType.getFieldAsInteger(ATTACK1_DMG_BASE, 0);
|
|
||||||
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_HALF, 0);
|
|
||||||
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK1_DAMAGE_FACTOR_QUARTER, 0);
|
|
||||||
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK1_DAMAGE_LOSS_FACTOR, 0);
|
|
||||||
final int damageDice = unitType.getFieldAsInteger(ATTACK1_DMG_DICE, 0);
|
|
||||||
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK1_DMG_SIDES_PER_DIE, 0);
|
|
||||||
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_DIST, 0);
|
|
||||||
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK1_DMG_SPILL_RADIUS, 0);
|
|
||||||
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK1_DMG_UPGRADE_AMT, 0);
|
|
||||||
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK1_TARGET_COUNT, 0);
|
|
||||||
final float projectileArc = unitType.getFieldAsFloat(ATTACK1_PROJECTILE_ARC, 0);
|
|
||||||
final String projectileArt = unitType.getFieldAsString(ATTACK1_MISSILE_ART, 0);
|
|
||||||
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK1_PROJECTILE_HOMING_ENABLED, 0);
|
|
||||||
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK1_PROJECTILE_SPEED, 0);
|
|
||||||
final int range = unitType.getFieldAsInteger(ATTACK1_RANGE, 0);
|
|
||||||
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK1_RANGE_MOTION_BUFFER, 0);
|
|
||||||
final boolean showUI = unitType.getFieldAsBoolean(ATTACK1_SHOW_UI, 0);
|
|
||||||
final EnumSet<CTargetType> targetsAllowed = CTargetType
|
|
||||||
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK1_TARGETS_ALLOWED, 0));
|
|
||||||
final String weaponSound = unitType.getFieldAsString(ATTACK1_WEAPON_SOUND, 0);
|
|
||||||
final CWeaponType weaponType = CWeaponType
|
|
||||||
.parseWeaponType(unitType.getFieldAsString(ATTACK1_WEAPON_TYPE, 0));
|
|
||||||
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
|
|
||||||
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime,
|
|
||||||
damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie,
|
|
||||||
damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc,
|
|
||||||
projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI,
|
|
||||||
targetsAllowed, weaponSound, weaponType));
|
|
||||||
}
|
|
||||||
if ((attacksEnabled & 0x2) != 0) {
|
|
||||||
// attack two
|
|
||||||
final float animationBackswingPoint = unitType.getFieldAsFloat(ATTACK2_BACKSWING_POINT, 0);
|
|
||||||
final float animationDamagePoint = unitType.getFieldAsFloat(ATTACK2_DAMAGE_POINT, 0);
|
|
||||||
final int areaOfEffectFullDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_FULL_DMG, 0);
|
|
||||||
final int areaOfEffectMediumDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_HALF_DMG, 0);
|
|
||||||
final int areaOfEffectSmallDamage = unitType.getFieldAsInteger(ATTACK2_AREA_OF_EFFECT_QUARTER_DMG, 0);
|
|
||||||
final EnumSet<CTargetType> areaOfEffectTargets = CTargetType
|
|
||||||
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_AREA_OF_EFFECT_TARGETS, 0));
|
|
||||||
final CAttackType attackType = CAttackType
|
|
||||||
.parseAttackType(unitType.getFieldAsString(ATTACK2_ATTACK_TYPE, 0));
|
|
||||||
final float cooldownTime = unitType.getFieldAsFloat(ATTACK2_COOLDOWN, 0);
|
|
||||||
final int damageBase = unitType.getFieldAsInteger(ATTACK2_DMG_BASE, 0);
|
|
||||||
final float damageFactorMedium = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_HALF, 0);
|
|
||||||
final float damageFactorSmall = unitType.getFieldAsFloat(ATTACK2_DAMAGE_FACTOR_QUARTER, 0);
|
|
||||||
final float damageLossFactor = unitType.getFieldAsFloat(ATTACK2_DAMAGE_LOSS_FACTOR, 0);
|
|
||||||
final int damageDice = unitType.getFieldAsInteger(ATTACK2_DMG_DICE, 0);
|
|
||||||
final int damageSidesPerDie = unitType.getFieldAsInteger(ATTACK2_DMG_SIDES_PER_DIE, 0);
|
|
||||||
final float damageSpillDistance = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_DIST, 0);
|
|
||||||
final float damageSpillRadius = unitType.getFieldAsFloat(ATTACK2_DMG_SPILL_RADIUS, 0);
|
|
||||||
final int damageUpgradeAmount = unitType.getFieldAsInteger(ATTACK2_DMG_UPGRADE_AMT, 0);
|
|
||||||
final int maximumNumberOfTargets = unitType.getFieldAsInteger(ATTACK2_TARGET_COUNT, 0);
|
|
||||||
final float projectileArc = unitType.getFieldAsFloat(ATTACK2_PROJECTILE_ARC, 0);
|
|
||||||
final String projectileArt = unitType.getFieldAsString(ATTACK2_MISSILE_ART, 0);
|
|
||||||
final boolean projectileHomingEnabled = unitType.getFieldAsBoolean(ATTACK2_PROJECTILE_HOMING_ENABLED, 0);
|
|
||||||
final int projectileSpeed = unitType.getFieldAsInteger(ATTACK2_PROJECTILE_SPEED, 0);
|
|
||||||
final int range = unitType.getFieldAsInteger(ATTACK2_RANGE, 0);
|
|
||||||
final float rangeMotionBuffer = unitType.getFieldAsFloat(ATTACK2_RANGE_MOTION_BUFFER, 0);
|
|
||||||
final boolean showUI = unitType.getFieldAsBoolean(ATTACK2_SHOW_UI, 0);
|
|
||||||
final EnumSet<CTargetType> targetsAllowed = CTargetType
|
|
||||||
.parseTargetTypeSet(unitType.getFieldAsString(ATTACK2_TARGETS_ALLOWED, 0));
|
|
||||||
final String weaponSound = unitType.getFieldAsString(ATTACK2_WEAPON_SOUND, 0);
|
|
||||||
final CWeaponType weaponType = CWeaponType
|
|
||||||
.parseWeaponType(unitType.getFieldAsString(ATTACK2_WEAPON_TYPE, 0));
|
|
||||||
attacks.add(createAttack(animationBackswingPoint, animationDamagePoint, areaOfEffectFullDamage,
|
|
||||||
areaOfEffectMediumDamage, areaOfEffectSmallDamage, areaOfEffectTargets, attackType, cooldownTime,
|
|
||||||
damageBase, damageFactorMedium, damageFactorSmall, damageLossFactor, damageDice, damageSidesPerDie,
|
|
||||||
damageSpillDistance, damageSpillRadius, damageUpgradeAmount, maximumNumberOfTargets, projectileArc,
|
|
||||||
projectileArt, projectileHomingEnabled, projectileSpeed, range, rangeMotionBuffer, showUI,
|
|
||||||
targetsAllowed, weaponSound, weaponType));
|
|
||||||
}
|
|
||||||
final int deathType = unitType.getFieldAsInteger(DEATH_TYPE, 0);
|
|
||||||
final boolean raise = (deathType & 0x1) != 0;
|
|
||||||
final boolean decay = (deathType & 0x2) != 0;
|
|
||||||
final String armorType = unitType.getFieldAsString(ARMOR_TYPE, 0);
|
|
||||||
final int defense = unitType.getFieldAsInteger(DEFENSE, 0);
|
|
||||||
final float impactZ = unitType.getFieldAsFloat(PROJECTILE_IMPACT_Z, 0);
|
|
||||||
final CDefenseType defenseType = CDefenseType.parseDefenseType(unitType.getFieldAsString(DEFENSE_TYPE, 0));
|
|
||||||
final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
|
final CUnit unit = new CUnit(handleId, playerIndex, x, y, life, typeId, facing, manaInitial, life, manaMaximum,
|
||||||
speed, defense, new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize,
|
speed, defense, unitTypeInstance);
|
||||||
classifications, attacks, armorType, raise, decay, defenseType, impactZ));
|
|
||||||
if (speed > 0) {
|
if (speed > 0) {
|
||||||
unit.add(simulation, CAbilityMove.INSTANCE);
|
unit.add(simulation, CAbilityMove.INSTANCE);
|
||||||
unit.add(simulation, CAbilityPatrol.INSTANCE);
|
unit.add(simulation, CAbilityPatrol.INSTANCE);
|
||||||
@ -275,7 +291,8 @@ public class CUnitData {
|
|||||||
attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType,
|
attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType,
|
||||||
cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range,
|
cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range,
|
||||||
rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt,
|
rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt,
|
||||||
projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets);
|
projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets,
|
||||||
|
areaOfEffectFullDamage, areaOfEffectTargets);
|
||||||
break;
|
break;
|
||||||
case MSPLASH:
|
case MSPLASH:
|
||||||
case ARTILLERY:
|
case ARTILLERY:
|
||||||
|
@ -2,10 +2,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
|
|||||||
|
|
||||||
import com.etheller.warsmash.util.WarsmashConstants;
|
import com.etheller.warsmash.util.WarsmashConstants;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
|
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.CSimulation;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener;
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
||||||
@ -15,16 +15,52 @@ public class CAttackOrder implements COrder {
|
|||||||
private boolean wasWithinPropWindow = false;
|
private boolean wasWithinPropWindow = false;
|
||||||
private final CUnitAttack unitAttack;
|
private final CUnitAttack unitAttack;
|
||||||
private final CWidget target;
|
private final CWidget target;
|
||||||
private int backswingLaunchTime;
|
private int damagePointLaunchTime;
|
||||||
|
private int backSwingTime;
|
||||||
|
private COrder moveOrder;
|
||||||
|
private int thisOrderCooldownEndTime;
|
||||||
|
private boolean wasInRange = false;
|
||||||
|
|
||||||
public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final CWidget target) {
|
public CAttackOrder(final CUnit unit, final CUnitAttack unitAttack, final CWidget target) {
|
||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
this.unitAttack = unitAttack;
|
this.unitAttack = unitAttack;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
|
createMoveOrder(unit, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createMoveOrder(final CUnit unit, final CWidget target) {
|
||||||
|
if ((target instanceof CUnit) && !(((CUnit) target).getUnitType().isBuilding())) {
|
||||||
|
this.moveOrder = new CMoveOrder(unit, (CUnit) target);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.moveOrder = new CMoveOrder(unit, target.getX(), target.getY());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean update(final CSimulation simulation) {
|
public boolean update(final CSimulation simulation) {
|
||||||
|
if (this.target.isDead()
|
||||||
|
|| !this.target.canBeTargetedBy(simulation, this.unit, this.unitAttack.getTargetsAllowed())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
float range = this.unitAttack.getRange();
|
||||||
|
if ((this.target instanceof CUnit) && (((CUnit) this.target).getCurrentOrder() instanceof CMoveOrder)
|
||||||
|
&& (this.damagePointLaunchTime != 0 /*
|
||||||
|
* only apply range motion buffer if they were already in range and
|
||||||
|
* attacked
|
||||||
|
*/)) {
|
||||||
|
range += this.unitAttack.getRangeMotionBuffer();
|
||||||
|
}
|
||||||
|
if (!this.unit.canReach(this.target, range)) {
|
||||||
|
if (this.moveOrder.update(simulation)) {
|
||||||
|
return true; // we just cant reach them
|
||||||
|
}
|
||||||
|
this.wasInRange = false;
|
||||||
|
this.damagePointLaunchTime = 0;
|
||||||
|
this.thisOrderCooldownEndTime = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.wasInRange = true;
|
||||||
final float prevX = this.unit.getX();
|
final float prevX = this.unit.getX();
|
||||||
final float prevY = this.unit.getY();
|
final float prevY = this.unit.getY();
|
||||||
final float deltaY = this.target.getY() - prevY;
|
final float deltaY = this.target.getY() - prevY;
|
||||||
@ -70,10 +106,13 @@ public class CAttackOrder implements COrder {
|
|||||||
final int cooldownEndTime = this.unit.getCooldownEndTime();
|
final int cooldownEndTime = this.unit.getCooldownEndTime();
|
||||||
final int currentTurnTick = simulation.getGameTurnTick();
|
final int currentTurnTick = simulation.getGameTurnTick();
|
||||||
if (this.wasWithinPropWindow) {
|
if (this.wasWithinPropWindow) {
|
||||||
if (this.backswingLaunchTime != 0) {
|
if (this.damagePointLaunchTime != 0) {
|
||||||
if (currentTurnTick >= this.backswingLaunchTime) {
|
if (currentTurnTick >= this.damagePointLaunchTime) {
|
||||||
simulation.createProjectile(this.unit, 0, this.target);
|
final int minDamage = this.unitAttack.getMinDamage();
|
||||||
this.backswingLaunchTime = 0;
|
final int maxDamage = this.unitAttack.getMaxDamage();
|
||||||
|
final int damage = simulation.getSeededRandom().nextInt(maxDamage - minDamage) + minDamage;
|
||||||
|
this.unitAttack.launch(simulation, this.unit, this.target, damage);
|
||||||
|
this.damagePointLaunchTime = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (currentTurnTick >= cooldownEndTime) {
|
else if (currentTurnTick >= cooldownEndTime) {
|
||||||
@ -84,15 +123,21 @@ public class CAttackOrder implements COrder {
|
|||||||
final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint()
|
final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint()
|
||||||
/ WarsmashConstants.SIMULATION_STEP_TIME);
|
/ WarsmashConstants.SIMULATION_STEP_TIME);
|
||||||
this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps);
|
this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps);
|
||||||
this.backswingLaunchTime = currentTurnTick + a1DamagePointSteps;
|
this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps;
|
||||||
this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK,
|
this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps;
|
||||||
CUnitAnimationListener.EMPTY, 1.0f);
|
this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps;
|
||||||
this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, CUnitAnimationListener.READY);
|
this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f,
|
||||||
|
true);
|
||||||
|
this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false);
|
||||||
|
}
|
||||||
|
else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) {
|
||||||
|
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, CUnitAnimationListener.READY,
|
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f,
|
||||||
1.0f);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,12 +7,12 @@ import java.util.List;
|
|||||||
import com.badlogic.gdx.math.Rectangle;
|
import com.badlogic.gdx.math.Rectangle;
|
||||||
import com.etheller.warsmash.util.WarsmashConstants;
|
import com.etheller.warsmash.util.WarsmashConstants;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
|
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.environment.PathingGrid.MovementType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
|
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.CSimulation;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener;
|
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWorldCollision;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
|
||||||
@ -24,6 +24,8 @@ public class CMoveOrder implements COrder {
|
|||||||
private List<Point2D.Float> path = null;
|
private List<Point2D.Float> path = null;
|
||||||
private final CPathfindingProcessor.GridMapping gridMapping;
|
private final CPathfindingProcessor.GridMapping gridMapping;
|
||||||
private final Point2D.Float target;
|
private final Point2D.Float target;
|
||||||
|
private int searchCycles = 0;
|
||||||
|
private CUnit followUnit;
|
||||||
|
|
||||||
public CMoveOrder(final CUnit unit, final float targetX, final float targetY) {
|
public CMoveOrder(final CUnit unit, final float targetX, final float targetY) {
|
||||||
this.unit = unit;
|
this.unit = unit;
|
||||||
@ -33,6 +35,15 @@ public class CMoveOrder implements COrder {
|
|||||||
this.target = new Point2D.Float(targetX, targetY);
|
this.target = new Point2D.Float(targetX, targetY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CMoveOrder(final CUnit unit, final CUnit followUnit) {
|
||||||
|
this.unit = unit;
|
||||||
|
this.gridMapping = CPathfindingProcessor.isCollisionSizeBetterSuitedForCorners(
|
||||||
|
unit.getUnitType().getCollisionSize()) ? CPathfindingProcessor.GridMapping.CORNERS
|
||||||
|
: CPathfindingProcessor.GridMapping.CELLS;
|
||||||
|
this.target = new Point2D.Float(followUnit.getX(), followUnit.getY());
|
||||||
|
this.followUnit = followUnit;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean update(final CSimulation simulation) {
|
public boolean update(final CSimulation simulation) {
|
||||||
final float prevX = this.unit.getX();
|
final float prevX = this.unit.getX();
|
||||||
@ -45,12 +56,15 @@ public class CMoveOrder implements COrder {
|
|||||||
final float startFloatingX = prevX;
|
final float startFloatingX = prevX;
|
||||||
final float startFloatingY = prevY;
|
final float startFloatingY = prevY;
|
||||||
if (this.path == null) {
|
if (this.path == null) {
|
||||||
this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target,
|
if (this.followUnit != null) {
|
||||||
movementType == null ? MovementType.FOOT : movementType, collisionSize);
|
this.target.x = this.followUnit.getX();
|
||||||
|
this.target.y = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||||
|
this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize, true);
|
||||||
System.out.println("init path " + this.path);
|
System.out.println("init path " + this.path);
|
||||||
// check for smoothing
|
// check for smoothing
|
||||||
if (!this.path.isEmpty()) {
|
if (!this.path.isEmpty()) {
|
||||||
this.path.add(this.target);
|
|
||||||
float lastX = startFloatingX;
|
float lastX = startFloatingX;
|
||||||
float lastY = startFloatingY;
|
float lastY = startFloatingY;
|
||||||
float smoothingGroupStartX = startFloatingX;
|
float smoothingGroupStartX = startFloatingX;
|
||||||
@ -95,16 +109,40 @@ public class CMoveOrder implements COrder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ((this.followUnit != null) && (this.path.size() > 1) && (this.target.distance(this.followUnit.getX(),
|
||||||
|
this.followUnit.getY()) > (0.1 * this.target.distance(this.unit.getX(), this.unit.getY())))) {
|
||||||
|
this.target.x = this.followUnit.getX();
|
||||||
|
this.target.y = this.followUnit.getY();
|
||||||
|
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||||
|
this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize,
|
||||||
|
this.searchCycles < 4);
|
||||||
|
System.out.println("new path (for target) " + this.path);
|
||||||
|
if (this.path.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
float currentTargetX;
|
float currentTargetX;
|
||||||
float currentTargetY;
|
float currentTargetY;
|
||||||
if (this.path.isEmpty()) {
|
if (this.path.isEmpty()) {
|
||||||
currentTargetX = this.target.x;
|
if (this.followUnit != null) {
|
||||||
currentTargetY = this.target.y;
|
currentTargetX = this.followUnit.getX();
|
||||||
|
currentTargetY = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentTargetX = this.target.x;
|
||||||
|
currentTargetY = this.target.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final Point2D.Float nextPathElement = this.path.get(0);
|
if ((this.followUnit != null) && (this.path.size() == 1)) {
|
||||||
currentTargetX = nextPathElement.x;
|
currentTargetX = this.followUnit.getX();
|
||||||
currentTargetY = nextPathElement.y;
|
currentTargetY = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final Point2D.Float nextPathElement = this.path.get(0);
|
||||||
|
currentTargetX = nextPathElement.x;
|
||||||
|
currentTargetY = nextPathElement.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float deltaX = currentTargetX - prevX;
|
float deltaX = currentTargetX - prevX;
|
||||||
@ -172,24 +210,46 @@ public class CMoveOrder implements COrder {
|
|||||||
&& !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) {
|
&& !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) {
|
||||||
this.unit.setPoint(nextX, nextY, worldCollision);
|
this.unit.setPoint(nextX, nextY, worldCollision);
|
||||||
if (done) {
|
if (done) {
|
||||||
|
// if we're making headway along the path then it's OK to start thinking fast
|
||||||
|
// again
|
||||||
|
if (travelDistance > 0) {
|
||||||
|
this.searchCycles = 0;
|
||||||
|
}
|
||||||
if (this.path.isEmpty()) {
|
if (this.path.isEmpty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
System.out.println(this.path);
|
||||||
final Float removed = this.path.remove(0);
|
final Float removed = this.path.remove(0);
|
||||||
System.out.println(
|
System.out.println(
|
||||||
"We think we reached " + removed + " because are at " + nextX + "," + nextY);
|
"We think we reached " + removed + " because we are at " + nextX + "," + nextY);
|
||||||
if (this.path.isEmpty()) {
|
final boolean emptyPath = this.path.isEmpty();
|
||||||
currentTargetX = this.target.x;
|
if (emptyPath) {
|
||||||
currentTargetY = this.target.y;
|
if (this.followUnit != null) {
|
||||||
|
currentTargetX = this.followUnit.getX();
|
||||||
|
currentTargetY = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentTargetX = this.target.x;
|
||||||
|
currentTargetY = this.target.y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final Point2D.Float firstPathElement = this.path.get(0);
|
if ((this.followUnit != null) && (this.path.size() == 1)) {
|
||||||
currentTargetX = firstPathElement.x;
|
currentTargetX = this.followUnit.getX();
|
||||||
currentTargetY = firstPathElement.y;
|
currentTargetY = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final Point2D.Float firstPathElement = this.path.get(0);
|
||||||
|
currentTargetX = firstPathElement.x;
|
||||||
|
currentTargetY = firstPathElement.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deltaY = currentTargetY - nextY;
|
||||||
|
deltaX = currentTargetX - nextX;
|
||||||
|
if ((deltaX == 0.000f) && (deltaY == 0.000f) && this.path.isEmpty()) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
deltaY = currentTargetY - prevY;
|
|
||||||
deltaX = currentTargetX - prevX;
|
|
||||||
System.out.println("new target: " + currentTargetX + "," + currentTargetY);
|
System.out.println("new target: " + currentTargetX + "," + currentTargetY);
|
||||||
System.out.println("new delta: " + deltaX + "," + deltaY);
|
System.out.println("new delta: " + deltaX + "," + deltaY);
|
||||||
goalAngleRad = Math.atan2(deltaY, deltaX);
|
goalAngleRad = Math.atan2(deltaY, deltaX);
|
||||||
@ -210,7 +270,7 @@ public class CMoveOrder implements COrder {
|
|||||||
if (absDelta >= propulsionWindow) {
|
if (absDelta >= propulsionWindow) {
|
||||||
if (this.wasWithinPropWindow) {
|
if (this.wasWithinPropWindow) {
|
||||||
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND,
|
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND,
|
||||||
CUnitAnimationListener.EMPTY, 1.0f);
|
SequenceUtils.EMPTY, 1.0f, true);
|
||||||
}
|
}
|
||||||
this.wasWithinPropWindow = false;
|
this.wasWithinPropWindow = false;
|
||||||
return false;
|
return false;
|
||||||
@ -219,20 +279,21 @@ public class CMoveOrder implements COrder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target,
|
if (this.followUnit != null) {
|
||||||
movementType == null ? MovementType.FOOT : movementType, collisionSize);
|
this.target.x = this.followUnit.getX();
|
||||||
|
this.target.y = this.followUnit.getY();
|
||||||
|
}
|
||||||
|
this.path = simulation.findNaiveSlowPath(this.unit, this.followUnit, startFloatingX, startFloatingY,
|
||||||
|
this.target, movementType == null ? MovementType.FOOT : movementType, collisionSize,
|
||||||
|
this.searchCycles < 4);
|
||||||
|
this.searchCycles++;
|
||||||
System.out.println("new path " + this.path);
|
System.out.println("new path " + this.path);
|
||||||
if (this.path.isEmpty()) {
|
if (this.path.isEmpty() || (this.searchCycles > 5)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
this.path.add(this.target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!this.wasWithinPropWindow) {
|
|
||||||
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK,
|
|
||||||
CUnitAnimationListener.EMPTY, 1.0f);
|
|
||||||
}
|
}
|
||||||
|
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.WALK, SequenceUtils.EMPTY, 1.0f,
|
||||||
|
true);
|
||||||
this.wasWithinPropWindow = true;
|
this.wasWithinPropWindow = true;
|
||||||
}
|
}
|
||||||
while (continueDistance > 0);
|
while (continueDistance > 0);
|
||||||
@ -241,8 +302,8 @@ public class CMoveOrder implements COrder {
|
|||||||
// If this happens, the unit is facing the wrong way, and has to turn before
|
// If this happens, the unit is facing the wrong way, and has to turn before
|
||||||
// moving.
|
// moving.
|
||||||
if (this.wasWithinPropWindow) {
|
if (this.wasWithinPropWindow) {
|
||||||
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND,
|
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f,
|
||||||
CUnitAnimationListener.EMPTY, 1.0f);
|
true);
|
||||||
}
|
}
|
||||||
this.wasWithinPropWindow = false;
|
this.wasWithinPropWindow = false;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing;
|
||||||
|
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -18,7 +19,8 @@ public class CPathfindingProcessor {
|
|||||||
private final CWorldCollision worldCollision;
|
private final CWorldCollision worldCollision;
|
||||||
private final Node[][] nodes;
|
private final Node[][] nodes;
|
||||||
private final Node[][] cornerNodes;
|
private final Node[][] cornerNodes;
|
||||||
private Node goal;
|
private final Node[] goalSet = new Node[4];
|
||||||
|
private int goals = 0;
|
||||||
|
|
||||||
public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) {
|
public CPathfindingProcessor(final PathingGrid pathingGrid, final CWorldCollision worldCollision) {
|
||||||
this.pathingGrid = pathingGrid;
|
this.pathingGrid = pathingGrid;
|
||||||
@ -54,12 +56,21 @@ public class CPathfindingProcessor {
|
|||||||
*/
|
*/
|
||||||
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
|
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
|
||||||
final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType,
|
final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType,
|
||||||
final float collisionSize) {
|
final float collisionSize, final boolean allowSmoothing) {
|
||||||
|
return findNaiveSlowPath(ignoreIntersectionsWithThisUnit, null, startX, startY, goal, movementType,
|
||||||
|
collisionSize, allowSmoothing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit,
|
||||||
|
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
|
||||||
|
final Point2D.Float goal, final PathingGrid.MovementType movementType, final float collisionSize,
|
||||||
|
final boolean allowSmoothing) {
|
||||||
final float goalX = goal.x;
|
final float goalX = goal.x;
|
||||||
final float goalY = goal.y;
|
final float goalY = goal.y;
|
||||||
if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize)
|
float weightForHittingWalls = 1E9f;
|
||||||
|| !isPathableDynamically(goalX, goalY, ignoreIntersectionsWithThisUnit, movementType)) {
|
if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) || !isPathableDynamically(goalX,
|
||||||
return Collections.emptyList();
|
goalY, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType)) {
|
||||||
|
weightForHittingWalls = 5E2f;
|
||||||
}
|
}
|
||||||
System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY);
|
System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY);
|
||||||
if ((startX == goalX) && (startY == goalY)) {
|
if ((startX == goalX) && (startY == goalY)) {
|
||||||
@ -78,7 +89,20 @@ public class CPathfindingProcessor {
|
|||||||
gridMapping = GridMapping.CELLS;
|
gridMapping = GridMapping.CELLS;
|
||||||
System.out.println("using cells");
|
System.out.println("using cells");
|
||||||
}
|
}
|
||||||
this.goal = searchGraph[gridMapping.getY(this.pathingGrid, goalY)][gridMapping.getX(this.pathingGrid, goalX)];
|
final int goalCellY = gridMapping.getY(this.pathingGrid, goalY);
|
||||||
|
final int goalCellX = gridMapping.getX(this.pathingGrid, goalX);
|
||||||
|
final Node mostLikelyGoal = searchGraph[goalCellY][goalCellX];
|
||||||
|
final double bestGoalDistance = mostLikelyGoal.point.distance(goalX, goalY);
|
||||||
|
Arrays.fill(this.goalSet, null);
|
||||||
|
this.goals = 0;
|
||||||
|
for (int i = goalCellX - 1; i <= (goalCellX + 1); i++) {
|
||||||
|
for (int j = goalCellY - 1; j <= (goalCellY + 1); j++) {
|
||||||
|
final Node possibleGoal = searchGraph[j][i];
|
||||||
|
if (possibleGoal.point.distance(goalX, goalY) <= bestGoalDistance) {
|
||||||
|
this.goalSet[this.goals++] = possibleGoal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
final int startGridY = gridMapping.getY(this.pathingGrid, startY);
|
final int startGridY = gridMapping.getY(this.pathingGrid, startY);
|
||||||
final int startGridX = gridMapping.getX(this.pathingGrid, startX);
|
final int startGridX = gridMapping.getX(this.pathingGrid, startX);
|
||||||
for (int i = 0; i < searchGraph.length; i++) {
|
for (int i = 0; i < searchGraph.length; i++) {
|
||||||
@ -133,8 +157,8 @@ public class CPathfindingProcessor {
|
|||||||
final Node possibleNode = searchGraph[cellY][cellX];
|
final Node possibleNode = searchGraph[cellY][cellX];
|
||||||
final float x = possibleNode.point.x;
|
final float x = possibleNode.point.x;
|
||||||
final float y = possibleNode.point.y;
|
final float y = possibleNode.point.y;
|
||||||
if (pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, collisionSize, x,
|
if (pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX,
|
||||||
y)) {
|
startY, movementType, collisionSize, x, y)) {
|
||||||
|
|
||||||
final double tentativeScore = possibleNode.point.distance(startX, startY);
|
final double tentativeScore = possibleNode.point.distance(startX, startY);
|
||||||
possibleNode.g = tentativeScore;
|
possibleNode.g = tentativeScore;
|
||||||
@ -142,19 +166,33 @@ public class CPathfindingProcessor {
|
|||||||
openSet.add(possibleNode);
|
openSet.add(possibleNode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
final double tentativeScore = weightForHittingWalls;
|
||||||
|
possibleNode.g = tentativeScore;
|
||||||
|
possibleNode.f = tentativeScore + h(possibleNode);
|
||||||
|
openSet.add(possibleNode);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!openSet.isEmpty()) {
|
while (!openSet.isEmpty()) {
|
||||||
Node current = openSet.poll();
|
Node current = openSet.poll();
|
||||||
if (current == this.goal) {
|
if (isGoal(current)) {
|
||||||
final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
|
final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
|
||||||
Direction lastCameFromDirection = null;
|
Direction lastCameFromDirection = null;
|
||||||
|
|
||||||
if ((current.cameFrom != null)
|
if ((current.cameFrom != null)
|
||||||
&& pathableBetween(ignoreIntersectionsWithThisUnit, current.cameFrom.point.x,
|
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||||
current.cameFrom.point.y, movementType, collisionSize, goalX, goalY)) {
|
current.point.x, current.point.y, movementType, collisionSize, goalX, goalY)
|
||||||
|
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||||
|
current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize,
|
||||||
|
current.point.x, current.point.y)
|
||||||
|
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||||
|
current.cameFrom.point.x, current.cameFrom.point.y, movementType, collisionSize, goalX,
|
||||||
|
goalY)
|
||||||
|
&& allowSmoothing) {
|
||||||
// do some basic smoothing to walk straight to the goal if it is not obstructed,
|
// do some basic smoothing to walk straight to the goal if it is not obstructed,
|
||||||
// skipping the last grid location
|
// skipping the last grid location
|
||||||
totalPath.addFirst(goal);
|
totalPath.addFirst(goal);
|
||||||
@ -172,8 +210,16 @@ public class CPathfindingProcessor {
|
|||||||
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)
|
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)
|
||||||
|| (current.cameFromDirection == null)) {
|
|| (current.cameFromDirection == null)) {
|
||||||
if ((current.cameFromDirection != null) || (lastNode == null)
|
if ((current.cameFromDirection != null) || (lastNode == null)
|
||||||
|| !pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType,
|
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
|
||||||
collisionSize, lastNode.point.x, lastNode.point.y)) {
|
ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType,
|
||||||
|
collisionSize, current.point.x, current.point.y)
|
||||||
|
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
|
||||||
|
ignoreIntersectionsWithThisSecondUnit, current.point.x, current.point.y,
|
||||||
|
movementType, collisionSize, lastNode.point.x, lastNode.point.y)
|
||||||
|
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
|
||||||
|
ignoreIntersectionsWithThisSecondUnit, startX, startY, movementType,
|
||||||
|
collisionSize, lastNode.point.x, lastNode.point.y)
|
||||||
|
|| !allowSmoothing) {
|
||||||
// Add the point if it's not the first one, or if we can only complete
|
// Add the point if it's not the first one, or if we can only complete
|
||||||
// the journey by specifically walking to the first one
|
// the journey by specifically walking to the first one
|
||||||
totalPath.addFirst(current.point);
|
totalPath.addFirst(current.point);
|
||||||
@ -187,8 +233,7 @@ public class CPathfindingProcessor {
|
|||||||
for (final Direction direction : Direction.VALUES) {
|
for (final Direction direction : Direction.VALUES) {
|
||||||
final float x = current.point.x + (direction.xOffset * 32);
|
final float x = current.point.x + (direction.xOffset * 32);
|
||||||
final float y = current.point.y + (direction.yOffset * 32);
|
final float y = current.point.y + (direction.yOffset * 32);
|
||||||
if (this.pathingGrid.contains(x, y) && pathableBetween(ignoreIntersectionsWithThisUnit, current.point.x,
|
if (this.pathingGrid.contains(x, y)) {
|
||||||
current.point.y, movementType, collisionSize, x, y)) {
|
|
||||||
double turnCost;
|
double turnCost;
|
||||||
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
|
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
|
||||||
turnCost = 0.25;
|
turnCost = 0.25;
|
||||||
@ -196,7 +241,11 @@ public class CPathfindingProcessor {
|
|||||||
else {
|
else {
|
||||||
turnCost = 0;
|
turnCost = 0;
|
||||||
}
|
}
|
||||||
final double tentativeScore = current.g + ((direction.length + turnCost) * 32);
|
double tentativeScore = current.g + ((direction.length + turnCost) * 32);
|
||||||
|
if (!pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||||
|
current.point.x, current.point.y, movementType, collisionSize, x, y)) {
|
||||||
|
tentativeScore += (direction.length) * weightForHittingWalls;
|
||||||
|
}
|
||||||
final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping
|
final Node neighbor = searchGraph[gridMapping.getY(this.pathingGrid, y)][gridMapping
|
||||||
.getX(this.pathingGrid, x)];
|
.getX(this.pathingGrid, x)];
|
||||||
if (tentativeScore < neighbor.g) {
|
if (tentativeScore < neighbor.g) {
|
||||||
@ -214,20 +263,24 @@ public class CPathfindingProcessor {
|
|||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit, final float startX, final float startY,
|
private boolean pathableBetween(final CUnit ignoreIntersectionsWithThisUnit,
|
||||||
|
final CUnit ignoreIntersectionsWithThisSecondUnit, final float startX, final float startY,
|
||||||
final PathingGrid.MovementType movementType, final float collisionSize, final float x, final float y) {
|
final PathingGrid.MovementType movementType, final float collisionSize, final float x, final float y) {
|
||||||
return this.pathingGrid.isPathable(x, y, movementType, collisionSize)
|
return this.pathingGrid.isPathable(x, y, movementType, collisionSize)
|
||||||
&& this.pathingGrid.isPathable(startX, y, movementType, collisionSize)
|
&& this.pathingGrid.isPathable(startX, y, movementType, collisionSize)
|
||||||
&& this.pathingGrid.isPathable(x, startY, movementType, collisionSize)
|
&& this.pathingGrid.isPathable(x, startY, movementType, collisionSize)
|
||||||
&& isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, movementType)
|
&& isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
|
||||||
&& isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, movementType)
|
movementType)
|
||||||
&& isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, movementType);
|
&& isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit,
|
||||||
|
ignoreIntersectionsWithThisSecondUnit, movementType)
|
||||||
|
&& isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit,
|
||||||
|
ignoreIntersectionsWithThisSecondUnit, movementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPathableDynamically(final float x, final float y, final CUnit ignoreIntersectionsWithThisUnit,
|
private boolean isPathableDynamically(final float x, final float y, final CUnit ignoreIntersectionsWithThisUnit,
|
||||||
final PathingGrid.MovementType movementType) {
|
final CUnit ignoreIntersectionsWithThisSecondUnit, final PathingGrid.MovementType movementType) {
|
||||||
return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y),
|
return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y),
|
||||||
ignoreIntersectionsWithThisUnit, movementType);
|
ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) {
|
public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) {
|
||||||
@ -242,8 +295,24 @@ public class CPathfindingProcessor {
|
|||||||
return n.g;
|
return n.g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isGoal(final Node n) {
|
||||||
|
for (int i = 0; i < this.goals; i++) {
|
||||||
|
if (n == this.goalSet[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public float h(final Node n) {
|
public float h(final Node n) {
|
||||||
return (float) n.point.distance(this.goal.point);
|
float bestDistance = 0;
|
||||||
|
for (int i = 0; i < this.goals; i++) {
|
||||||
|
final float possibleDistance = (float) n.point.distance(this.goalSet[i].point);
|
||||||
|
if (possibleDistance > bestDistance) {
|
||||||
|
bestDistance = possibleDistance; // always overestimate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Node {
|
public static final class Node {
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
|
public enum CAllianceType {
|
||||||
|
PASSIVE,
|
||||||
|
HELP_REQUEST,
|
||||||
|
HELP_RESPONSE,
|
||||||
|
SHARED_XP,
|
||||||
|
SHARED_SPELLS,
|
||||||
|
SHARED_VISION,
|
||||||
|
SHARED_CONTROL,
|
||||||
|
SHARED_ADVANCED_CONTROL,
|
||||||
|
RESCUABLE,
|
||||||
|
SHARED_VISION_FORCED;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
|
public enum CMapControl {
|
||||||
|
USER,
|
||||||
|
COMPUTER,
|
||||||
|
RESCUABLE,
|
||||||
|
NEUTRAL,
|
||||||
|
CREEP,
|
||||||
|
NONE;
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import com.etheller.warsmash.util.WarsmashConstants;
|
||||||
|
|
||||||
|
public class CPlayer {
|
||||||
|
private final int id;
|
||||||
|
private int colorIndex;
|
||||||
|
private final CMapControl controlType;
|
||||||
|
private String name;
|
||||||
|
private final CRace race;
|
||||||
|
private final float[] startLocation;
|
||||||
|
private final EnumSet<CRacePreference> racePrefs;
|
||||||
|
private int gold;
|
||||||
|
private int lumber;
|
||||||
|
private final EnumSet<CAllianceType>[] alliances = new EnumSet[WarsmashConstants.MAX_PLAYERS];
|
||||||
|
|
||||||
|
public CPlayer(final int id, final CMapControl controlType, final String name, final CRace race,
|
||||||
|
final float[] startLocation) {
|
||||||
|
this.id = id;
|
||||||
|
this.colorIndex = id;
|
||||||
|
this.controlType = controlType;
|
||||||
|
this.name = name;
|
||||||
|
this.race = race;
|
||||||
|
this.startLocation = startLocation;
|
||||||
|
this.racePrefs = EnumSet.noneOf(CRacePreference.class);
|
||||||
|
for (int i = 0; i < this.alliances.length; i++) {
|
||||||
|
if (i == this.id) {
|
||||||
|
// player is fully allied with self
|
||||||
|
this.alliances[i] = EnumSet.allOf(CAllianceType.class);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.alliances[i] = EnumSet.noneOf(CAllianceType.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColorIndex() {
|
||||||
|
return this.colorIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CMapControl getControlType() {
|
||||||
|
return this.controlType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CRace getRace() {
|
||||||
|
return this.race;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRacePrefSet(final CRacePreference racePref) {
|
||||||
|
return this.racePrefs.contains(racePref);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlliance(final CPlayer otherPlayer, final CAllianceType allianceType, final boolean value) {
|
||||||
|
final EnumSet<CAllianceType> alliancesWithOtherPlayer = this.alliances[otherPlayer.getId()];
|
||||||
|
if (value) {
|
||||||
|
alliancesWithOtherPlayer.add(allianceType);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alliancesWithOtherPlayer.remove(allianceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlliance(final CPlayer otherPlayer, final CAllianceType allianceType) {
|
||||||
|
return hasAlliance(otherPlayer.getId(), allianceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlliance(final int otherPlayerIndex, final CAllianceType allianceType) {
|
||||||
|
final EnumSet<CAllianceType> alliancesWithOtherPlayer = this.alliances[otherPlayerIndex];
|
||||||
|
return alliancesWithOtherPlayer.contains(allianceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGold() {
|
||||||
|
return this.gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLumber() {
|
||||||
|
return this.lumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float[] getStartLocation() {
|
||||||
|
return this.startLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGold(final int gold) {
|
||||||
|
this.gold = gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLumber(final int lumber) {
|
||||||
|
this.lumber = lumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColorIndex(final int colorIndex) {
|
||||||
|
this.colorIndex = colorIndex;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
public interface CPlayerController {
|
public interface CPlayerController {
|
||||||
boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId);
|
boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId);
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
|
public enum CRace {
|
||||||
|
HUMAN(1),
|
||||||
|
ORC(2),
|
||||||
|
UNDEAD(3),
|
||||||
|
NIGHTELF(4),
|
||||||
|
DEMON(5),
|
||||||
|
OTHER(7);
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
private CRace(final int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CRace parseRace(final int race) {
|
||||||
|
// TODO: this is bad time complexity (slow) but we're only doing it on startup
|
||||||
|
for (final CRace raceEnum : values()) {
|
||||||
|
if (raceEnum.getId() == race) {
|
||||||
|
return raceEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
|
||||||
|
|
||||||
|
public enum CRacePreference {
|
||||||
|
HUMAN,
|
||||||
|
ORC,
|
||||||
|
NIGHTELF,
|
||||||
|
UNDEAD,
|
||||||
|
DEMON,
|
||||||
|
RANDOM,
|
||||||
|
USER_SELECTABLE;
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
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.combat.projectile.CAttackProjectile;
|
|
||||||
|
|
||||||
public interface ProjectileCreator {
|
|
||||||
CAttackProjectile create(CSimulation simulation, CUnit source, int attackIndex, CWidget target);
|
|
||||||
}
|
|
@ -0,0 +1,19 @@
|
|||||||
|
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.combat.attacks.CUnitAttackInstant;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
|
||||||
|
|
||||||
|
public interface SimulationRenderController {
|
||||||
|
CAttackProjectile createAttackProjectile(CSimulation simulation, float launchX, float launchY, float launchFacing,
|
||||||
|
CUnit source, CUnitAttackMissile attack, CWidget target, float damage, int bounceIndex);
|
||||||
|
|
||||||
|
void createInstantAttackEffect(CSimulation cSimulation, CUnit source, CUnitAttackInstant attack, CWidget target);
|
||||||
|
|
||||||
|
void spawnUnitDamageSound(CUnit damagedUnit, final String weaponSound, String armorType);
|
||||||
|
|
||||||
|
void removeUnit(CUnit unit);
|
||||||
|
}
|
@ -2,6 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.GL20;
|
||||||
import com.badlogic.gdx.graphics.Texture;
|
import com.badlogic.gdx.graphics.Texture;
|
||||||
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
|
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
|
||||||
@ -21,19 +23,25 @@ import com.etheller.warsmash.parsers.fdf.frames.TextureFrame;
|
|||||||
import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
|
import com.etheller.warsmash.parsers.fdf.frames.UIFrame;
|
||||||
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener;
|
import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener;
|
||||||
import com.etheller.warsmash.util.FastNumberFormat;
|
import com.etheller.warsmash.util.FastNumberFormat;
|
||||||
|
import com.etheller.warsmash.util.ImageUtils;
|
||||||
import com.etheller.warsmash.viewer5.Scene;
|
import com.etheller.warsmash.viewer5.Scene;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
|
||||||
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.StandSequence;
|
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.CommandCardIcon;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderUnit;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitStateListener;
|
||||||
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityStop;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType;
|
||||||
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
|
||||||
|
|
||||||
public class MeleeUI {
|
public class MeleeUI implements CUnitStateListener {
|
||||||
private final DataSource dataSource;
|
private final DataSource dataSource;
|
||||||
private final Viewport uiViewport;
|
private final Viewport uiViewport;
|
||||||
private final FreeTypeFontGenerator fontGenerator;
|
private final FreeTypeFontGenerator fontGenerator;
|
||||||
@ -43,6 +51,10 @@ public class MeleeUI {
|
|||||||
private GameUI rootFrame;
|
private GameUI rootFrame;
|
||||||
private UIFrame consoleUI;
|
private UIFrame consoleUI;
|
||||||
private UIFrame resourceBar;
|
private UIFrame resourceBar;
|
||||||
|
private StringFrame resourceBarGoldText;
|
||||||
|
private StringFrame resourceBarLumberText;
|
||||||
|
private StringFrame resourceBarSupplyText;
|
||||||
|
private StringFrame resourceBarUpkeepText;
|
||||||
private UIFrame timeIndicator;
|
private UIFrame timeIndicator;
|
||||||
private UIFrame unitPortrait;
|
private UIFrame unitPortrait;
|
||||||
private StringFrame unitLifeText;
|
private StringFrame unitLifeText;
|
||||||
@ -69,6 +81,10 @@ public class MeleeUI {
|
|||||||
private StringFrame armorInfoPanelIconLevel;
|
private StringFrame armorInfoPanelIconLevel;
|
||||||
private InfoPanelIconBackdrops damageBackdrops;
|
private InfoPanelIconBackdrops damageBackdrops;
|
||||||
private InfoPanelIconBackdrops defenseBackdrops;
|
private InfoPanelIconBackdrops defenseBackdrops;
|
||||||
|
private RenderUnit selectedUnit;
|
||||||
|
|
||||||
|
// TODO remove this & replace with FDF
|
||||||
|
private final Texture activeButtonTexture;
|
||||||
|
|
||||||
public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator,
|
public MeleeUI(final DataSource dataSource, final Viewport uiViewport, final FreeTypeFontGenerator fontGenerator,
|
||||||
final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) {
|
final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) {
|
||||||
@ -79,6 +95,8 @@ public class MeleeUI {
|
|||||||
this.war3MapViewer = war3MapViewer;
|
this.war3MapViewer = war3MapViewer;
|
||||||
this.rootFrameListener = rootFrameListener;
|
this.rootFrameListener = rootFrameListener;
|
||||||
|
|
||||||
|
this.activeButtonTexture = ImageUtils.getBLPTexture(war3MapViewer.mapMpq,
|
||||||
|
"UI\\Widgets\\Console\\Human\\CommandButton\\human-activebutton.blp");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,7 +107,7 @@ public class MeleeUI {
|
|||||||
// =================================
|
// =================================
|
||||||
// Load skins and templates
|
// Load skins and templates
|
||||||
// =================================
|
// =================================
|
||||||
this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 1), this.uiViewport,
|
this.rootFrame = new GameUI(this.dataSource, GameUI.loadSkin(this.dataSource, 3), this.uiViewport,
|
||||||
this.fontGenerator, this.uiScene, this.war3MapViewer);
|
this.fontGenerator, this.uiScene, this.war3MapViewer);
|
||||||
try {
|
try {
|
||||||
this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc");
|
this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc");
|
||||||
@ -118,6 +136,16 @@ public class MeleeUI {
|
|||||||
// put it in the "TOPRIGHT" corner.
|
// put it in the "TOPRIGHT" corner.
|
||||||
this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0);
|
this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 0);
|
||||||
this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0));
|
this.resourceBar.addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.consoleUI, FramePoint.TOPRIGHT, 0, 0));
|
||||||
|
this.resourceBarGoldText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarGoldText", 0);
|
||||||
|
this.resourceBarGoldText.setText("500");
|
||||||
|
this.resourceBarLumberText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarLumberText", 0);
|
||||||
|
this.resourceBarLumberText.setText("150");
|
||||||
|
this.resourceBarSupplyText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarSupplyText", 0);
|
||||||
|
this.resourceBarSupplyText.setText("153/100");
|
||||||
|
this.resourceBarSupplyText.setColor(Color.RED);
|
||||||
|
this.resourceBarUpkeepText = (StringFrame) this.rootFrame.getFrameByName("ResourceBarUpkeepText", 0);
|
||||||
|
this.resourceBarUpkeepText.setText("High Upkeep");
|
||||||
|
this.resourceBarUpkeepText.setColor(Color.RED);
|
||||||
|
|
||||||
// Create the Time Indicator (clock)
|
// Create the Time Indicator (clock)
|
||||||
this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0);
|
this.timeIndicator = this.rootFrame.createFrame("TimeOfDayIndicator", this.rootFrame, 0, 0);
|
||||||
@ -174,6 +202,25 @@ public class MeleeUI {
|
|||||||
|
|
||||||
public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) {
|
public void render(final SpriteBatch batch, final BitmapFont font20, final GlyphLayout glyphLayout) {
|
||||||
this.rootFrame.render(batch, font20, glyphLayout);
|
this.rootFrame.render(batch, font20, glyphLayout);
|
||||||
|
|
||||||
|
if (this.selectedUnit != null) {
|
||||||
|
font20.setColor(Color.WHITE);
|
||||||
|
|
||||||
|
final COrder currentOrder = this.selectedUnit.getSimulationUnit().getCurrentOrder();
|
||||||
|
for (final CommandCardIcon commandCardIcon : this.selectedUnit.getCommandCardIcons()) {
|
||||||
|
batch.draw(commandCardIcon.getTexture(), 1235 + (86.8f * commandCardIcon.getX()),
|
||||||
|
190 - (88 * commandCardIcon.getY()), 78f, 78f);
|
||||||
|
if (((currentOrder != null) && (currentOrder.getOrderId() == commandCardIcon.getOrderId()))
|
||||||
|
|| ((currentOrder == null) && (commandCardIcon.getOrderId() == CAbilityStop.ORDER_ID))) {
|
||||||
|
final int blendDstFunc = batch.getBlendDstFunc();
|
||||||
|
final int blendSrcFunc = batch.getBlendSrcFunc();
|
||||||
|
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
|
||||||
|
batch.draw(this.activeButtonTexture, 1235 + (86.8f * commandCardIcon.getX()),
|
||||||
|
190 - (88 * commandCardIcon.getY()), 78f, 78f);
|
||||||
|
batch.setBlendFunction(blendSrcFunc, blendDstFunc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void portraitTalk() {
|
public void portraitTalk() {
|
||||||
@ -196,12 +243,12 @@ public class MeleeUI {
|
|||||||
this.portraitCameraManager.updateCamera();
|
this.portraitCameraManager.updateCamera();
|
||||||
if ((this.modelInstance != null)
|
if ((this.modelInstance != null)
|
||||||
&& (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) {
|
&& (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) {
|
||||||
StandSequence.randomPortraitSequence(this.modelInstance);
|
SequenceUtils.randomPortraitSequence(this.modelInstance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void talk() {
|
public void talk() {
|
||||||
StandSequence.randomPortraitTalkSequence(this.modelInstance);
|
SequenceUtils.randomPortraitTalkSequence(this.modelInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedUnit(final RenderUnit unit) {
|
public void setSelectedUnit(final RenderUnit unit) {
|
||||||
@ -220,7 +267,7 @@ public class MeleeUI {
|
|||||||
}
|
}
|
||||||
this.modelInstance = (MdxComplexInstance) portraitModel.addInstance();
|
this.modelInstance = (MdxComplexInstance) portraitModel.addInstance();
|
||||||
this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel);
|
this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel);
|
||||||
this.modelInstance.setSequenceLoopMode(1);
|
this.modelInstance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP);
|
||||||
this.modelInstance.setScene(this.portraitScene);
|
this.modelInstance.setScene(this.portraitScene);
|
||||||
this.modelInstance.setVertexColor(unit.instance.vertexColor);
|
this.modelInstance.setVertexColor(unit.instance.vertexColor);
|
||||||
this.modelInstance.setTeamColor(unit.playerIndex);
|
this.modelInstance.setTeamColor(unit.playerIndex);
|
||||||
@ -229,8 +276,15 @@ public class MeleeUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectUnit(final RenderUnit unit) {
|
public void selectUnit(RenderUnit unit) {
|
||||||
|
if ((unit != null) && unit.getSimulationUnit().isDead()) {
|
||||||
|
unit = null;
|
||||||
|
}
|
||||||
|
if (this.selectedUnit != null) {
|
||||||
|
this.selectedUnit.getSimulationUnit().removeStateListener(this);
|
||||||
|
}
|
||||||
this.portrait.setSelectedUnit(unit);
|
this.portrait.setSelectedUnit(unit);
|
||||||
|
this.selectedUnit = unit;
|
||||||
if (unit == null) {
|
if (unit == null) {
|
||||||
this.simpleNameValue.setText("");
|
this.simpleNameValue.setText("");
|
||||||
this.unitLifeText.setText("");
|
this.unitLifeText.setText("");
|
||||||
@ -245,6 +299,7 @@ public class MeleeUI {
|
|||||||
this.armorInfoPanelIconLevel.setText("");
|
this.armorInfoPanelIconLevel.setText("");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
unit.getSimulationUnit().addStateListener(this);
|
||||||
this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName());
|
this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName());
|
||||||
String classText = null;
|
String classText = null;
|
||||||
for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) {
|
for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) {
|
||||||
@ -291,8 +346,13 @@ public class MeleeUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.armorIcon.setVisible(true);
|
this.armorIcon.setVisible(true);
|
||||||
this.armorIconBackdrop.setTexture(
|
final Texture defenseTexture = this.defenseBackdrops
|
||||||
this.defenseBackdrops.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()));
|
.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType());
|
||||||
|
if (defenseTexture == null) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
unit.getSimulationUnit().getUnitType().getDefenseType() + " can't find texture!");
|
||||||
|
}
|
||||||
|
this.armorIconBackdrop.setTexture(defenseTexture);
|
||||||
this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense()));
|
this.armorInfoPanelIconValue.setText(Integer.toString(unit.getSimulationUnit().getDefense()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,8 +369,8 @@ public class MeleeUI {
|
|||||||
this.uiViewport.project(this.projectionTemp1);
|
this.uiViewport.project(this.projectionTemp1);
|
||||||
this.uiViewport.project(this.projectionTemp2);
|
this.uiViewport.project(this.projectionTemp2);
|
||||||
|
|
||||||
this.tempRect.x = this.projectionTemp1.x;
|
this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX();
|
||||||
this.tempRect.y = this.projectionTemp1.y;
|
this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY();
|
||||||
this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x;
|
this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x;
|
||||||
this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y;
|
this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y;
|
||||||
this.portrait.portraitScene.camera.viewport(this.tempRect);
|
this.portrait.portraitScene.camera.viewport(this.tempRect);
|
||||||
@ -325,10 +385,11 @@ public class MeleeUI {
|
|||||||
for (int index = 0; index < attackTypes.length; index++) {
|
for (int index = 0; index < attackTypes.length; index++) {
|
||||||
final CodeKeyType attackType = attackTypes[index];
|
final CodeKeyType attackType = attackTypes[index];
|
||||||
String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix;
|
String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix;
|
||||||
try {
|
final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
|
||||||
this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
|
if (suffixTexture != null) {
|
||||||
|
this.damageBackdropTextures[index] = suffixTexture;
|
||||||
}
|
}
|
||||||
catch (final Exception exc) {
|
else {
|
||||||
skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey();
|
skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey();
|
||||||
this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
|
this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
|
||||||
}
|
}
|
||||||
@ -369,4 +430,20 @@ public class MeleeUI {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void lifeChanged() {
|
||||||
|
if (this.selectedUnit.getSimulationUnit().isDead()) {
|
||||||
|
selectUnit(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.unitLifeText
|
||||||
|
.setText(FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getLife()) + " / "
|
||||||
|
+ this.selectedUnit.getSimulationUnit().getMaximumLife());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderUnit getSelectedUnit() {
|
||||||
|
return this.selectedUnit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,14 @@ public class DesktopLauncher {
|
|||||||
config.gles30ContextMajorVersion = 3;
|
config.gles30ContextMajorVersion = 3;
|
||||||
config.gles30ContextMinorVersion = 3;
|
config.gles30ContextMinorVersion = 3;
|
||||||
config.samples = 16;
|
config.samples = 16;
|
||||||
config.fullscreen = false;
|
config.vSyncEnabled = false;
|
||||||
if (config.fullscreen) {
|
config.foregroundFPS = 0;
|
||||||
|
config.backgroundFPS = 0;
|
||||||
|
if ((arg.length > 0) && "-windowed".equals(arg[0])) {
|
||||||
|
config.fullscreen = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config.fullscreen = true;
|
||||||
final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode();
|
final DisplayMode desktopDisplayMode = LwjglApplicationConfiguration.getDesktopDisplayMode();
|
||||||
config.width = desktopDisplayMode.width;
|
config.width = desktopDisplayMode.width;
|
||||||
config.height = desktopDisplayMode.height;
|
config.height = desktopDisplayMode.height;
|
||||||
|
Loading…
Reference in New Issue
Block a user