Update to include race, basic target types, better combat UI and behaviors

This commit is contained in:
Retera 2020-09-22 22:30:07 -04:00
parent bf16b4f698
commit cfa5f952de
67 changed files with 2513 additions and 744 deletions

View File

@ -1,15 +1,23 @@
[DataSources]
Count=5
Type00=Folder
Path00="E:\Backups\Warcraft III 1.30 but dead\War3mod.mpq"
Type01=Folder
Path01="E:\Backups\Warcraft\Data\127"
Type02=Folder
Path02="..\..\resources"
Type03=Folder
Path03="E:\Backups\Warsmash\Data"
Count=7
Type00=MPQ
Path00="E:\Games\Warcraft III Patch 1.22\war3.mpq"
Type01=MPQ
Path01="E:\Games\Warcraft III Patch 1.22\War3x.mpq"
Type02=MPQ
Path02="E:\Games\Warcraft III Patch 1.22\War3xlocal.mpq"
Type03=MPQ
Path03="E:\Games\Warcraft III Patch 1.22\War3Patch.mpq"
Type04=Folder
Path04="."
Path04="..\..\resources"
Type05=Folder
Path05="E:\Backups\Warsmash\Data"
Type06=Folder
Path06="."
[Map]
FilePath="PitchRoll.w3x"
//FilePath="CombatUnitTests.w3x"
FilePath="PitchRoll.w3x"
//FilePath="PlayerPeasants.w3m"
//FilePath="FireLord.w3x"
//FilePath="Maps\Campaign\NightElf03.w3m"

View 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"

View File

@ -7,7 +7,6 @@ import java.util.Arrays;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
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.FolderDataSourceDescriptor;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.util.DataSourceFileHandle;
import com.etheller.warsmash.viewer5.Camera;
import com.etheller.warsmash.viewer5.CanvasProvider;
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.MdxHandler;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvider {
private static final boolean SPIN = false;
@ -68,21 +67,21 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.viewer.addHandler(new MdxHandler());
this.viewer.enableAudio();
final Scene scene = this.viewer.addWorldScene();
// scene.enableAudio();
final Scene scene = this.viewer.addSimpleScene();
scene.enableAudio();
this.cameraManager = new CameraManager();
this.cameraManager.setupCamera(scene);
// 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",
new PathSolver() {
@Override
public SolvedPath solve(final String src, final Object solverParams) {
return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
}
}, null);
// new PathSolver() {
// @Override
// public SolvedPath solve(final String src, final Object solverParams) {
// return new SolvedPath(src, src.substring(src.lastIndexOf('.')), true);
// }
// }, null);
// final EventObjectEmitterObject evt = this.mainModel.getEventObjects().get(1);
// 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(evt.name);
this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);
// this.mainInstance = (MdxComplexInstance) this.mainModel.addInstance(0);
this.mainInstance.setScene(scene);
final int animIndex = 0;
this.modelCamera = this.mainModel.cameras.get(animIndex);
this.mainInstance.setSequence(animIndex);
this.mainInstance.setSequenceLoopMode(4);
// this.mainInstance.setScene(scene);
//
// final int animIndex = 0;
// this.modelCamera = this.mainModel.cameras.get(animIndex);
// this.mainInstance.setSequence(animIndex);
//
// this.mainInstance.setSequenceLoopMode(SequenceLoopMode.LOOP_TO_NEXT_ANIMATION);
// acolytesHarvestingSceneJoke2(scene);
singleModelScene(scene, "Buildings\\Undead\\Necropolis\\Necropolis.mdx", "birth");
System.out.println("Loaded");
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.setSequenceLoopMode(2);
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
}
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()) {
if (s.getName().toLowerCase().startsWith(animName)) {
animIndex = model2.getSequences().indexOf(s);
break;
}
}
instance3.setSequence(animIndex);
instance3.setSequenceLoopMode(2);
instance3.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
}
private void acolytesHarvestingScene(final Scene scene) {
@ -196,7 +198,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
}
acolyteInstance.setSequence(animIndex);
acolyteInstance.setSequenceLoopMode(2);
acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
final double angle = ((Math.PI * 2) / 5) * i;
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
@ -209,7 +211,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
effectInstance.setSequence(1);
effectInstance.setSequenceLoopMode(2);
effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
effectInstance.localRotation.setFromAxisRad(0, 0, 1, (float) (angle));
@ -228,7 +230,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
mineInstance.setSequence(2);
mineInstance.setSequenceLoopMode(2);
mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
}
private void acolytesHarvestingSceneJoke2(final Scene scene) {
@ -260,7 +262,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
}
acolyteInstance.setSequence(animIndex);
acolyteInstance.setSequenceLoopMode(2);
acolyteInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
final double angle = ((Math.PI * 2) / 5) * i;
acolyteInstance.localLocation.x = (float) Math.cos(angle) * 256;
@ -273,7 +275,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
effectInstance.setSequence(1);
effectInstance.setSequenceLoopMode(2);
effectInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
effectInstance.localLocation.x = (float) Math.cos(angle) * 256;
effectInstance.localLocation.y = (float) Math.sin(angle) * 256;
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.z = 2;
mineInstance.setSequenceLoopMode(2);
mineInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
final MdxModel mineModel2 = (MdxModel) this.viewer
.load("abilities\\spells\\undead\\unsummon\\unsummontarget.mdx", new PathSolver() {
@Override
@ -309,7 +311,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
mineInstance2.setSequence(0);
mineInstance2.setSequenceLoopMode(2);
mineInstance2.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
}
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();
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();
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 final float[] cameraPositionTemp = new float[3];
private final float[] cameraTargetTemp = new float[3];
private boolean firstFrame = true;
private final boolean firstFrame = true;
@Override
public void render() {
Gdx.gl30.glBindVertexArray(VAO);
if (SPIN) {
this.cameraManager.horizontalAngle += 0.01;
this.cameraManager.horizontalAngle += 0.0001;
if (this.cameraManager.horizontalAngle > (2 * Math.PI)) {
this.cameraManager.horizontalAngle = 0;
}
@ -384,14 +386,14 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.mainInstance.setSequence(sequence);
this.mainInstance.frame += (int) (Gdx.graphics.getRawDeltaTime() * 1000);
}
if (this.firstFrame) {
final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource,
"Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3"));
music.setVolume(0.2f);
music.setLooping(true);
music.play();
this.firstFrame = false;
}
// if (this.firstFrame) {
// final Music music = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource,
// "Sound\\Ambient\\DoodadEffects\\FinalCinematic.mp3"));
// music.setVolume(0.2f);
// music.setLooping(true);
// music.play();
// this.firstFrame = false;
// }
}
@Override
@ -445,7 +447,7 @@ public class WarsmashGdxGame extends ApplicationAdapter implements CanvasProvide
this.zoomFactor = 0.1f;
this.horizontalAngle = (float) (Math.PI / 2);
this.verticalAngle = (float) (Math.PI / 4);
this.distance = 1000;
this.distance = 500;
this.position = new Vector3();
this.target = new Vector3(0, 0, 50);
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.farClippingPlane);
}
else {
this.camera.perspective(70, this.camera.getAspect(), 100, 5000);
}
this.camera.moveToAndFace(this.position, this.target, this.worldUp);
}

View File

@ -30,8 +30,7 @@ import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.badlogic.gdx.utils.viewport.ExtendViewport;
import com.etheller.warsmash.datasources.CompoundDataSourceDescriptor;
import com.etheller.warsmash.datasources.DataSource;
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.ReplaceableIds;
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.rendersim.CommandCardIcon;
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;
public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProvider, InputProcessor {
@ -74,18 +70,14 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// libGDX stuff
private OrthographicCamera uiCamera;
private BitmapFont font;
private BitmapFont font24;
private BitmapFont font20;
private SpriteBatch batch;
private Viewport uiViewport;
private ExtendViewport uiViewport;
private GlyphLayout glyphLayout;
private Texture consoleUITexture;
private RenderUnit selectedUnit;
private int selectedSoundCount = 0;
private Texture activeButtonTexture;
private Rectangle minimap;
private Rectangle minimapFilledArea;
@ -171,20 +163,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
final float w = Gdx.graphics.getWidth();
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(
new DataSourceFileHandle(this.viewer.dataSource, "fonts\\FRIZQT__.TTF"));
final FreeTypeFontParameter fontParam = new FreeTypeFontParameter();
fontParam.size = 32;
this.font = fontGenerator.generateFont(fontParam);
fontParam.size = 24;
this.font24 = fontGenerator.generateFont(fontParam);
fontParam.size = 20;
this.font20 = fontGenerator.generateFont(fontParam);
this.glyphLayout = new GlyphLayout();
@ -193,10 +176,10 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
// height
// Height is multiplied by aspect ratio.
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.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.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++) {
this.teamColors[i] = ImageUtils.getBLPTexture(this.viewer.dataSource,
"ReplaceableTextures\\" + ReplaceableIds.getPathString(1) + ReplaceableIds.getIdString(i) + ".blp");
@ -236,8 +216,8 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
Gdx.input.setInputProcessor(this);
// final Music music = Gdx.audio.newMusic(
// new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\War2IntroMusic.mp3"));
// final Music music = Gdx.audio
// .newMusic(new DataSourceFileHandle(this.viewer.dataSource, "Sound\\Music\\mp3Music\\Undead2.mp3"));
// music.setVolume(0.2f);
// music.setLooping(true);
// music.play();
@ -277,6 +257,27 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
});
this.meleeUI.main();
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
@ -313,32 +314,11 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
this.font.setColor(Color.YELLOW);
final String fpsString = "FPS: " + Gdx.graphics.getFramesPerSecond();
this.glyphLayout.setText(this.font, fpsString);
this.font.draw(this.batch, fpsString, (this.uiViewport.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);
if (this.selectedUnit != null) {
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);
}
}
}
final Rectangle playableMapArea = this.viewer.terrain.getPlayableMapArea();
for (final RenderUnit unit : this.viewer.units) {
if (unit.playerIndex >= WarsmashConstants.MAX_PLAYERS) {
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];
this.batch.draw(minimapIcon,
this.minimapFilledArea.x + (((unit.location[0] - this.viewer.terrain.centerOffset[0])
/ ((this.viewer.terrain.columns - 1) * 128f)) * this.minimapFilledArea.width),
this.minimapFilledArea.y + (((unit.location[1] - this.viewer.terrain.centerOffset[1])
/ ((this.viewer.terrain.rows - 1) * 128f)) * this.minimapFilledArea.height),
this.minimapFilledArea.x
+ (((unit.location[0] - playableMapArea.getX()) / (playableMapArea.getWidth()))
* this.minimapFilledArea.width),
this.minimapFilledArea.y
+ (((unit.location[1] - playableMapArea.getY()) / (playableMapArea.getHeight()))
* this.minimapFilledArea.height),
4, 4);
}
this.batch.end();
@ -411,14 +393,9 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
final float portraitTestHeight = (100 / 480f) * 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();
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);
updateUIScene();
this.meleeUI.resize();
}
@ -621,20 +598,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
clickLocationTemp2.y = screenY;
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)) {
final float clickX = (clickLocationTemp2.x - this.minimapFilledArea.x) / this.minimapFilledArea.width;
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) {
final RenderUnit rayPickUnit = this.viewer.rayPickUnit(screenX, worldScreenY);
if ((rayPickUnit != null) && (this.selectedUnit != null)
&& (rayPickUnit.playerIndex != this.selectedUnit.playerIndex)) {
if (this.viewer.orderSmart(rayPickUnit)) {
this.meleeUI.portraitTalk();
this.selectedSoundCount = 0;
if (this.meleeUI.getSelectedUnit() != null) {
if ((rayPickUnit != null) && (rayPickUnit.playerIndex != this.meleeUI.getSelectedUnit().playerIndex)
&& !rayPickUnit.getSimulationUnit().isDead()) {
if (this.viewer.orderSmart(rayPickUnit)) {
if (this.meleeUI.getSelectedUnit().soundset.yesAttack.playUnitResponse(
this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) {
this.meleeUI.portraitTalk();
}
this.selectedSoundCount = 0;
}
}
}
else {
this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
System.out.println(clickLocationTemp);
this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0);
final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128);
final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128);
System.out.println(x + "," + y);
this.viewer.terrain.logRomp(x, y);
if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) {
this.meleeUI.portraitTalk();
this.selectedSoundCount = 0;
else {
this.viewer.getClickLocation(clickLocationTemp, screenX, (int) worldScreenY);
System.out.println(clickLocationTemp);
this.viewer.showConfirmation(clickLocationTemp, 0, 1, 0);
final int x = (int) ((clickLocationTemp.x - this.viewer.terrain.centerOffset[0]) / 128);
final int y = (int) ((clickLocationTemp.y - this.viewer.terrain.centerOffset[1]) / 128);
System.out.println(x + "," + y);
this.viewer.terrain.logRomp(x, y);
if (this.viewer.orderSmart(clickLocationTemp.x, clickLocationTemp.y)) {
if (this.meleeUI.getSelectedUnit().soundset.yes.playUnitResponse(
this.viewer.worldScene.audioContext, this.meleeUI.getSelectedUnit())) {
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);
if (!selectedUnits.isEmpty()) {
final RenderUnit unit = selectedUnits.get(0);
final boolean selectionChanged = this.selectedUnit != unit;
final boolean selectionChanged = this.meleeUI.getSelectedUnit() != unit;
boolean playedNewSound = false;
if (selectionChanged) {
this.selectedSoundCount = 0;
}
this.selectedUnit = unit;
if (unit.soundset != null) {
UnitAckSound ackSoundToPlay = unit.soundset.what;
UnitSound ackSoundToPlay = unit.soundset.what;
final int pissedSoundCount = unit.soundset.pissed.getSoundCount();
int soundIndex;
if ((this.selectedSoundCount >= 3) && (pissedSoundCount > 0)) {
@ -688,8 +658,7 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
else {
soundIndex = (int) (Math.random() * ackSoundToPlay.getSoundCount());
}
if (ackSoundToPlay.play(this.viewer.worldScene.audioContext, unit.location[0], unit.location[1],
soundIndex)) {
if (ackSoundToPlay.playUnitResponse(this.viewer.worldScene.audioContext, unit, soundIndex)) {
this.selectedSoundCount++;
if ((this.selectedSoundCount - 3) >= pissedSoundCount) {
this.selectedSoundCount = 0;
@ -705,7 +674,6 @@ public class WarsmashGdxMapGame extends ApplicationAdapter implements CanvasProv
}
}
else {
this.selectedUnit = null;
this.meleeUI.selectUnit(null);
}
}

View File

@ -12,6 +12,7 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
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.Viewport;
import com.etheller.warsmash.datasources.DataSource;
@ -62,7 +63,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
this.viewport = viewport;
this.uiScene = uiScene;
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.fontGenerator = fontGenerator;
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) {
if (viewport instanceof ExtendViewport) {
return (fdfX / 0.8f) * ((ExtendViewport) viewport).getMinWorldWidth();
}
return (fdfX / 0.8f) * viewport.getWorldWidth();
}
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();
}
@ -325,8 +338,13 @@ public final class GameUI extends AbstractUIFrame implements UIFrame {
}
Texture texture = this.pathToTexture.get(path);
if (texture == null) {
texture = ImageUtils.getBLPTexture(this.dataSource, path);
this.pathToTexture.put(path, texture);
try {
texture = ImageUtils.getBLPTexture(this.dataSource, path);
this.pathToTexture.put(path, texture);
}
catch (final Exception exc) {
}
}
return texture;
}

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.utils.viewport.Viewport;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel;
import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode;
public class SpriteFrame extends AbstractRenderableFrame {
@ -24,7 +25,7 @@ public class SpriteFrame extends AbstractRenderableFrame {
}
if (model != null) {
this.instance = (MdxComplexInstance) model.addInstance();
this.instance.setSequenceLoopMode(1);
this.instance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP);
this.instance.setScene(this.scene);
this.instance.setLocation(this.renderBounds.x, this.renderBounds.y, 0);
}

View File

@ -36,7 +36,9 @@ public class TextureFrame extends AbstractRenderableFrame {
file = gameUI.getSkinField(file);
}
final Texture texture = gameUI.loadTexture(file);
setTexture(texture);
if (texture != null) {
setTexture(texture);
}
}
public void setTexture(final Texture texture) {

View File

@ -32,8 +32,8 @@ public class Player {
this.allyLowPriorities = ParseUtils.readUInt32(stream);
this.allyHighPriorities = ParseUtils.readUInt32(stream);
if (version > 30) {
enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream);
enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream);
this.enemyLowPrioritiesFlags = ParseUtils.readUInt32(stream);
this.enemyHighPrioritiesFlags = ParseUtils.readUInt32(stream);
}
}
@ -51,4 +51,44 @@ public class Player {
public int getByteLength() {
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;
}
}

View File

@ -38,34 +38,36 @@ public class Quadtree<T> {
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) {
for (int i = 0; i < this.nodes.size; i++) {
final Node<T> node = this.nodes.get(i);
if ((node.object != sourceObjectToIgnore) && node.bounds.overlaps(bounds)) {
return true;
if (node.bounds.overlaps(bounds)) {
if (intersector.onIntersect(node.object)) {
return true;
}
}
}
return false;
}
else {
if (this.northeast.bounds.overlaps(bounds)) {
if (this.northeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
if (this.northeast.intersect(bounds, intersector)) {
return true;
}
}
if (this.northwest.bounds.overlaps(bounds)) {
if (this.northwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
if (this.northwest.intersect(bounds, intersector)) {
return true;
}
}
if (this.southwest.bounds.overlaps(bounds)) {
if (this.southwest.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
if (this.southwest.intersect(bounds, intersector)) {
return true;
}
}
if (this.southeast.bounds.overlaps(bounds)) {
if (this.southeast.intersectsAnythingOtherThan(sourceObjectToIgnore, bounds)) {
if (this.southeast.intersect(bounds, intersector)) {
return true;
}
}

View 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);
}

View File

@ -3,7 +3,6 @@ package com.etheller.warsmash.viewer5;
public class AudioContext {
private boolean running = false;
public Listener listener = new Listener();
public long lastUnitResponseEndTimeMillis;
public AudioDestination destination = new AudioDestination() {
};

View File

@ -13,7 +13,7 @@ public class AttachmentInstance implements UpdatableObject {
final MdxModel internalModel = attachment.internalModel;
final MdxComplexInstance internalInstance = (MdxComplexInstance) internalModel.addInstance();
internalInstance.setSequenceLoopMode(2);
internalInstance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
internalInstance.dontInheritScaling = false;
internalInstance.hide();
internalInstance.setParent(instance.nodes[attachment.objectId]);

View File

@ -279,20 +279,27 @@ public class EventObjectEmitterObject extends GenericObject implements EmitterOb
final String[] fileNames = ((String) animSoundsRow.get("FileNames")).split(",");
final GenericResource[] resources = new GenericResource[fileNames.length];
for (int i = 0; i < fileNames.length; i++) {
final String pathString = pathSolver.solve(
((String) animSoundsRow.get("DirectoryBase")) + fileNames[i],
model.solverParams).finalSrc;
final GenericResource genericResource = viewer.loadGeneric(pathString,
FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString));
if (genericResource == null) {
throw new IllegalStateException("Null sound: " + fileNames[i]);
final String path = ((String) animSoundsRow.get("DirectoryBase")) + fileNames[i];
try {
final String pathString = pathSolver.solve(path, model.solverParams).finalSrc;
final GenericResource genericResource = viewer.loadGeneric(pathString,
FetchDataTypeName.ARRAY_BUFFER, new LoadGenericSoundCallback(pathString));
if (genericResource == null) {
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
for (final GenericResource resource : resources) {
this.decodedBuffers.add((Sound) resource.data);
if (resource != null) {
this.decodedBuffers.add((Sound) resource.data);
}
}
this.ok = true;
}

View File

@ -29,6 +29,9 @@ public class EventObjectSnd extends EmittedObject<MdxComplexInstance, EventObjec
final MdxNode node = instance.nodes[emitterObject.index];
final AudioContext audioContext = scene.audioContext;
final List<Sound> decodedBuffers = emitterObject.decodedBuffers;
if (decodedBuffers.isEmpty()) {
return;
}
final AudioPanner panner = audioContext.createPanner();
final AudioBufferSource source = audioContext.createBufferSource();
final Vector3 location = node.worldLocation;

View File

@ -42,10 +42,11 @@ public class MdxComplexInstance extends ModelInstance {
public MdxNode[] nodes;
public SkeletalNode[] sortedNodes;
public int frame = 0;
public float floatingFrame = 0;
// Global sequences
public int counter = 0;
public int sequence = -1;
public int sequenceLoopMode = 0;
public SequenceLoopMode sequenceLoopMode = SequenceLoopMode.NEVER_LOOP;
public boolean sequenceEnded = false;
public float[] vertexColor = { 1, 1, 1, 1 };
// Particles do not spawn when the sequence is -1, or when the sequence finished
@ -523,34 +524,40 @@ public class MdxComplexInstance extends ModelInstance {
if (sequenceId != -1) {
final Sequence sequence = model.sequences.get(sequenceId);
final long[] interval = sequence.getInterval();
final int frameTime = (int) (dt * 1000 * this.animationSpeed);
final float frameTime = (dt * 1000 * this.animationSpeed);
this.frame += frameTime;
this.counter += frameTime;
final int lastIntegerFrame = this.frame;
this.floatingFrame += frameTime;
this.frame = (int) this.floatingFrame;
final int integerFrameTime = this.frame - lastIntegerFrame;
this.counter += integerFrameTime;
this.allowParticleSpawn = true;
if (this.frame >= interval[1]) {
if ((this.sequenceLoopMode == 2) || ((this.sequenceLoopMode == 1) && (sequence.getFlags() == 0))) {
this.frame = (int) interval[0]; // TODO not cast
if (this.floatingFrame >= interval[1]) {
if ((this.sequenceLoopMode == SequenceLoopMode.ALWAYS_LOOP)
|| ((this.sequenceLoopMode == SequenceLoopMode.MODEL_LOOP) && (sequence.getFlags() == 0))) {
this.floatingFrame = this.frame = (int) interval[0]; // TODO not cast
this.resetEventEmitters();
}
else if (this.sequenceLoopMode == 4) { // faux queued animation mode
final int framesPast = this.frame - (int) interval[1];
else if (this.sequenceLoopMode == SequenceLoopMode.LOOP_TO_NEXT_ANIMATION) { // faux queued animation
// mode
final float framesPast = this.floatingFrame - interval[1];
final List<Sequence> sequences = model.sequences;
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.resetEventEmitters();
this.forced = true;
}
else {
this.frame = (int) interval[1]; // TODO not cast
this.counter -= frameTime;
this.floatingFrame = this.frame = (int) interval[1]; // TODO not cast
this.counter -= integerFrameTime;
this.allowParticleSpawn = false;
}
if (this.sequenceLoopMode == 3) {
if (this.sequenceLoopMode == SequenceLoopMode.NEVER_LOOP_AND_HIDE_WHEN_DONE) {
hide();
}
@ -636,10 +643,12 @@ public class MdxComplexInstance extends ModelInstance {
if ((id < 0) || (id > (sequences.size() - 1))) {
this.sequence = -1;
this.frame = 0;
this.floatingFrame = 0;
this.allowParticleSpawn = false;
}
else {
this.frame = (int) sequences.get(id).getInterval()[0]; // TODO not cast
this.floatingFrame = this.frame;
this.sequenceEnded = false;
}
@ -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
* spawned effects
*/
public MdxComplexInstance setSequenceLoopMode(final int mode) {
public MdxComplexInstance setSequenceLoopMode(final SequenceLoopMode mode) {
this.sequenceLoopMode = mode;
return this;

View File

@ -109,11 +109,11 @@ public final class SdSequence<TYPE> {
// with the default value.
if (framesBuilder.get(framesBuilder.size() - 1) != end) {
framesBuilder.add(end);
valuesBuilder.add(valuesBuilder.get(0));
valuesBuilder.add(valuesBuilder.get(valuesBuilder.size() - 1));
if (interpolationType > 1) {
inTansBuilder.add(inTansBuilder.get(0));
outTansBuilder.add(outTansBuilder.get(0));
inTansBuilder.add(inTansBuilder.get(inTansBuilder.size() - 1));
outTansBuilder.add(outTansBuilder.get(outTansBuilder.size() - 1));
}
}
}

View File

@ -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
}

View File

@ -24,7 +24,7 @@ public class TgaFile {
/**
* Read a TGA image from a file
*
*
* @param file
* @return
* @throws FileNotFoundException
@ -37,7 +37,7 @@ public class TgaFile {
/**
* Read a TGA image from an input stream.
*
*
* @param name
* @param stream
* @return
@ -142,7 +142,7 @@ public class TgaFile {
/**
* Write a BufferedImage to a TGA file BufferedImages should be TYPE_INT_ARGB or
* TYPE_INT_RGB
*
*
* @param src
* @param file
* @throws IOException

View File

@ -19,6 +19,7 @@ public class AnimationTokens {
public static enum SecondaryTag {
ALTERNATE,
ALTERNATEEX,
BONE,
CHAIN,
CHANNEL,
COMPLETE,
@ -56,6 +57,7 @@ public class AnimationTokens {
SMALL,
SPIKED,
SPIN,
SPELL,
SWIM,
TALK,
THIRD,

View File

@ -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.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();
@ -87,7 +91,8 @@ public class StandSequence {
}
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);
filtered.sort(STAND_SEQUENCE_COMPARATOR);
@ -101,7 +106,7 @@ public class StandSequence {
break;
}
if ((Math.random() * 10) > rarity) {
if (((Math.random() * 10) > rarity) && allowRarityVariations) {
return filtered.get(i);
}
}
@ -207,17 +212,22 @@ public class StandSequence {
}
}
public static void randomSequence(final MdxComplexInstance target, final PrimaryTag animationName,
final EnumSet<SecondaryTag> secondaryAnimationTags) {
public static Sequence randomSequence(final MdxComplexInstance target, final PrimaryTag animationName,
final EnumSet<SecondaryTag> secondaryAnimationTags, final boolean allowRarityVariations) {
final MdxModel model = (MdxModel) target.model;
final List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences);
final IndexedSequence sequence = selectSequence(animationName, secondaryAnimationTags, sequences,
allowRarityVariations);
if (sequence != null) {
target.setSequence(sequence.index);
return sequence.sequence;
}
else {
randomStandSequence(target);
if (!secondaryAnimationTags.isEmpty()) {
return randomSequence(target, animationName, EMPTY, allowRarityVariations);
}
return null;
}
}
}

View File

@ -24,6 +24,8 @@ public class SplatModel {
private final Texture texture;
private final List<Batch> batches;
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,
final List<Consumer<SplatMover>> unitMapping) {
@ -31,16 +33,49 @@ public class SplatModel {
this.batches = new ArrayList<>();
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[]> uvs = new ArrayList<>();
final List<int[]> indices = 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) {
final Consumer<SplatMover> unit = ((unitMapping != null) && (idx < unitMapping.size()))
? unitMapping.get(idx)
: null;
final float[] locs = locations.get(idx);
final float[] locs = this.locations.get(idx);
final float x0 = locs[0];
final float y0 = locs[1];
final float x1 = locs[2];
@ -61,7 +96,8 @@ public class SplatModel {
* ((int) Math.ceil((x1 - x0) / 128.0) + 1);
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;
if (numVertsToCrate > MAX_VERTICES) {
@ -124,8 +160,7 @@ public class SplatModel {
}
}
}
if (unit != null) {
unit.accept(splatMover);
if (this.splatInstances != null) {
batchRenderUnits.add(splatMover);
while (splatMover.indices.size() < maxPossibleFaces) {
@ -140,7 +175,6 @@ public class SplatModel {
if (indices.size() > 0) {
this.addBatch(gl, vertices, uvs, indices, batchRenderUnits);
}
}
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 uvXScale;
private int vertexBuffer;
private final int startOffset;
private final int start;
private int startOffset;
private int start;
private final List<float[]> vertices = new ArrayList<>();
private final List<float[]> uvs = 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.indicesStartOffset = indicesStartOffset;
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) {
@ -325,5 +370,40 @@ public class SplatModel {
gl.glBufferSubData(GL30.GL_ARRAY_BUFFER, this.uvsOffset + ((this.startOffset / 3) * 2),
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));
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -1,127 +1,23 @@
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;
public class UnitSoundset {
public final UnitAckSound what;
public final UnitAckSound pissed;
public final UnitAckSound yesAttack;
public final UnitAckSound yes;
public final UnitAckSound ready;
public final UnitAckSound warcry;
public final UnitSound what;
public final UnitSound pissed;
public final UnitSound yesAttack;
public final UnitSound yes;
public final UnitSound ready;
public final UnitSound warcry;
public UnitSoundset(final DataSource dataSource, final DataTable unitAckSounds, final String soundName) {
this.what = UnitAckSound.create(dataSource, unitAckSounds, soundName, "What");
this.pissed = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Pissed");
this.yesAttack = UnitAckSound.create(dataSource, unitAckSounds, soundName, "YesAttack");
this.yes = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Yes");
this.ready = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Ready");
this.warcry = UnitAckSound.create(dataSource, unitAckSounds, soundName, "Warcry");
this.what = UnitSound.create(dataSource, unitAckSounds, soundName, "What");
this.pissed = UnitSound.create(dataSource, unitAckSounds, soundName, "Pissed");
this.yesAttack = UnitSound.create(dataSource, unitAckSounds, soundName, "YesAttack");
this.yes = UnitSound.create(dataSource, unitAckSounds, soundName, "Yes");
this.ready = UnitSound.create(dataSource, unitAckSounds, soundName, "Ready");
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();
}
}
}

View File

@ -17,6 +17,8 @@ import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import javax.imageio.ImageIO;
import org.apache.commons.compress.utils.IOUtils;
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.MdxHandler;
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.w3x.SplatModel.SplatMover;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.Terrain.Splat;
import com.etheller.warsmash.viewer5.handlers.w3x.rendersim.RenderAttackInstant;
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.RenderUnit;
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.CAbilityAttack;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackInstant;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttackMissile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.projectile.CAttackProjectile;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.CWidgetAbilityTargetCheckReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.PointAbilityTargetCheckReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ProjectileCreator;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRenderController;
import mpq.MPQArchive;
import mpq.MPQException;
@ -129,7 +136,7 @@ public class War3MapViewer extends ModelViewer {
public MappedData unitMetaData = new MappedData();
public List<RenderUnit> units = new ArrayList<>();
public List<RenderItem> items = new ArrayList<>();
public List<RenderAttackProjectile> projectiles = new ArrayList<>();
public List<RenderEffect> projectiles = new ArrayList<>();
public boolean unitsReady;
public War3Map mapMpq;
public PathSolver mapPathSolver = PathSolver.DEFAULT;
@ -143,6 +150,7 @@ public class War3MapViewer extends ModelViewer {
public List<SplatModel> selModels = new ArrayList<>();
public List<RenderUnit> selected = new ArrayList<>();
private DataTable unitAckSoundsTable;
private DataTable unitCombatSoundsTable;
private DataTable miscData;
private DataTable unitGlobalStrings;
private MdxComplexInstance confirmationInstance;
@ -159,6 +167,8 @@ public class War3MapViewer extends ModelViewer {
private final List<SelectionCircleSize> selectionCircleSizes = new ArrayList<>();
private final Map<CUnit, RenderUnit> unitToRenderPeer = new HashMap<>();
public War3MapViewer(final DataSource dataSource, final CanvasProvider canvas) {
super(dataSource, canvas);
this.gameDataSource = dataSource;
@ -229,6 +239,11 @@ public class War3MapViewer extends ModelViewer {
try (InputStream terrainSlkStream = this.dataSource.getResourceAsStream("UI\\SoundInfo\\UnitAckSounds.slk")) {
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);
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("UI\\MiscData.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
@ -236,6 +251,9 @@ public class War3MapViewer extends ModelViewer {
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscData.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\MiscGame.txt")) {
this.miscData.readTXT(miscDataTxtStream, true);
}
this.unitGlobalStrings = new DataTable(worldEditStrings);
try (InputStream miscDataTxtStream = this.dataSource.getResourceAsStream("Units\\UnitGlobalStrings.txt")) {
this.unitGlobalStrings.readTXT(miscDataTxtStream, true);
@ -294,7 +312,8 @@ public class War3MapViewer extends ModelViewer {
try (InputStream mapStream = compoundDataSource.getResourceAsStream(tileset + ".mpq")) {
if (mapStream == null) {
tilesetSource = new CompoundDataSource(Arrays.asList(compoundDataSource,
new SubdirDataSource(compoundDataSource, tileset + ".mpq/")));
new SubdirDataSource(compoundDataSource, tileset + ".mpq/"),
new SubdirDataSource(compoundDataSource, "_tilesets/" + tileset + ".w3mod/")));
}
else {
final byte[] mapData = IOUtils.toByteArray(mapStream);
@ -306,7 +325,8 @@ public class War3MapViewer extends ModelViewer {
}
catch (final IOException exc) {
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) {
@ -339,7 +359,7 @@ public class War3MapViewer extends ModelViewer {
final MdxModel confirmation = (MdxModel) load("UI\\Feedback\\Confirmation\\Confirmation.mdx",
PathSolver.DEFAULT, null);
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.setScene(this.worldScene);
@ -352,57 +372,124 @@ public class War3MapViewer extends ModelViewer {
final Warcraft3MapObjectData modifications = this.mapMpq.readModifications();
this.simulation = new CSimulation(this.miscData, modifications.getUnits(), modifications.getAbilities(),
new ProjectileCreator() {
new SimulationRenderController() {
private final Map<String, UnitSound> keyToCombatSound = new HashMap<>();
@Override
public CAttackProjectile create(final CSimulation simulation, final CUnit source,
final int attackIndex, final CWidget target) {
public CAttackProjectile createAttackProjectile(final CSimulation simulation, final float launchX,
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 int a1ProjectileSpeed = simulation.getUnitData().getA1ProjectileSpeed(typeId);
final float a1ProjectileArc = simulation.getUnitData().getA1ProjectileArc(typeId);
String a1MissileArt = simulation.getUnitData().getA1MissileArt(typeId);
final int a1MinDamage = simulation.getUnitData().getA1MinDamage(typeId);
final int a1MaxDamage = simulation.getUnitData().getA1MaxDamage(typeId);
final int projectileSpeed = unitAttack.getProjectileSpeed();
final float projectileArc = unitAttack.getProjectileArc();
String missileArt = unitAttack.getProjectileArt();
final float projectileLaunchX = simulation.getUnitData().getProjectileLaunchX(typeId);
final float projectileLaunchY = simulation.getUnitData().getProjectileLaunchY(typeId);
final float projectileLaunchZ = simulation.getUnitData().getProjectileLaunchZ(typeId);
final int damage = War3MapViewer.this.seededRandom.nextInt(a1MaxDamage - a1MinDamage)
+ a1MinDamage;
if (a1MissileArt.toLowerCase().endsWith(".mdl")) {
a1MissileArt = a1MissileArt.substring(0, a1MissileArt.length() - 4);
if (missileArt.toLowerCase().endsWith(".mdl")) {
missileArt = missileArt.substring(0, missileArt.length() - 4);
}
if (!a1MissileArt.toLowerCase().endsWith(".mdx")) {
a1MissileArt += ".mdx";
if (!missileArt.toLowerCase().endsWith(".mdx")) {
missileArt += ".mdx";
}
final float facing = (float) Math.toRadians(source.getFacing());
final float facing = launchFacing;
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 x = (launchX + (projectileLaunchY * cosFacing)) + (projectileLaunchX * sinFacing);
final float y = (launchY + (projectileLaunchY * sinFacing)) - (projectileLaunchX * cosFacing);
final float height = War3MapViewer.this.terrain.getGroundHeight(x, y) + source.getFlyHeight()
+ projectileLaunchZ;
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);
final MdxComplexInstance modelInstance = (MdxComplexInstance) model.addInstance();
modelInstance.setTeamColor(source.getPlayerIndex());
modelInstance.setScene(War3MapViewer.this.worldScene);
StandSequence.randomBirthSequence(modelInstance);
if (bounceIndex == 0) {
SequenceUtils.randomBirthSequence(modelInstance);
}
else {
SequenceUtils.randomStandSequence(modelInstance);
}
modelInstance.setLocation(x, y, height);
final RenderAttackProjectile renderAttackProjectile = new RenderAttackProjectile(
simulationAttackProjectile, modelInstance, height, a1ProjectileArc, War3MapViewer.this);
simulationAttackProjectile, modelInstance, height, projectileArc, War3MapViewer.this);
War3MapViewer.this.projectiles.add(renderAttackProjectile);
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,
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) {
this.loadDoodadsAndDestructibles(modifications);
@ -596,6 +683,7 @@ public class War3MapViewer extends ModelViewer {
MutableGameObject row = null;
String path = null;
Splat unitShadowSplat = null;
BufferedImage buildingPathingPixelMap = null;
// Hardcoded?
WorldEditorDataType type = null;
@ -693,14 +781,23 @@ public class War3MapViewer extends ModelViewer {
}
final String pathingTexture = row.getFieldAsString(UNIT_PATHING, 0);
if ((pathingTexture != null) && (pathingTexture.length() > 0) && !"_".equals(pathingTexture)) {
BufferedImage bufferedImage = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
if (bufferedImage == null) {
bufferedImage = TgaFile.readTGA(pathingTexture,
this.mapMpq.getResourceAsStream(pathingTexture));
this.filePathToPathingMap.put(pathingTexture.toLowerCase(), bufferedImage);
buildingPathingPixelMap = this.filePathToPathingMap.get(pathingTexture.toLowerCase());
if (buildingPathingPixelMap == null) {
if (pathingTexture.toLowerCase().endsWith(".tga")) {
buildingPathingPixelMap = TgaFile.readTGA(pathingTexture,
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],
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);
@ -724,19 +821,12 @@ public class War3MapViewer extends ModelViewer {
portraitModel = model;
}
if (type == WorldEditorDataType.UNITS) {
float angle;
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 float angle = (float) Math.toDegrees(unit.getAngle());
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,
simulationUnit);
this.unitToRenderPeer.put(simulationUnit, renderUnit);
this.units.add(renderUnit);
if (unitShadowSplat != null) {
unitShadowSplat.unitMapping.add(new Consumer<SplatModel.SplatMover>() {
@ -782,10 +872,10 @@ public class War3MapViewer extends ModelViewer {
for (final RenderUnit unit : this.units) {
unit.updateAnimations(this);
}
final Iterator<RenderAttackProjectile> projectileIterator = this.projectiles.iterator();
final Iterator<RenderEffect> projectileIterator = this.projectiles.iterator();
while (projectileIterator.hasNext()) {
final RenderAttackProjectile projectile = projectileIterator.next();
if (projectile.updateAnimations(this)) {
final RenderEffect projectile = projectileIterator.next();
if (projectile.updateAnimations(this, Gdx.graphics.getDeltaTime())) {
projectileIterator.remove();
}
}
@ -793,7 +883,7 @@ public class War3MapViewer extends ModelViewer {
final MdxComplexInstance instance = item.instance;
final MdxComplexInstance mdxComplexInstance = instance;
if (mdxComplexInstance.sequenceEnded || (mdxComplexInstance.sequence == -1)) {
StandSequence.randomStandSequence(mdxComplexInstance);
SequenceUtils.randomStandSequence(mdxComplexInstance);
}
}
for (final Doodad item : this.doodads) {
@ -801,12 +891,13 @@ public class War3MapViewer extends ModelViewer {
if ((instance instanceof MdxComplexInstance) && (instance != this.confirmationInstance)) {
final MdxComplexInstance mdxComplexInstance = (MdxComplexInstance) instance;
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) {
this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME;
this.simulation.update();
@ -942,7 +1033,8 @@ public class War3MapViewer extends ModelViewer {
for (final RenderUnit unit : this.units) {
final MdxComplexInstance instance = unit.instance;
if (instance.isVisible(this.worldScene.camera)
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)) {
&& instance.intersectRayWithCollision(gdxRayHeap, intersectionHeap)
&& !unit.getSimulationUnit().isDead()) {
entity = unit;
}
}
@ -1081,7 +1173,6 @@ public class War3MapViewer extends ModelViewer {
final Vector2 target = PointAbilityTargetCheckReceiver.INSTANCE.getTarget();
if (target != null) {
ability.onOrder(this.simulation, unit.getSimulationUnit(), mousePosHeap, false);
unit.soundset.yes.play(this.worldScene.audioContext, unit.location[0], unit.location[1]);
ordered = true;
}
else {
@ -1114,8 +1205,6 @@ public class War3MapViewer extends ModelViewer {
final CWidget targetWidget = CWidgetAbilityTargetCheckReceiver.INSTANCE.getTarget();
if (targetWidget != null) {
ability.onOrder(this.simulation, unit.getSimulationUnit(), targetWidget, false);
unit.soundset.yesAttack.play(this.worldScene.audioContext, unit.location[0],
unit.location[1]);
ordered = true;
}
else {
@ -1136,8 +1225,8 @@ public class War3MapViewer extends ModelViewer {
}
public void standOnRepeat(final MdxComplexInstance instance) {
instance.setSequenceLoopMode(2);
StandSequence.randomStandSequence(instance);
instance.setSequenceLoopMode(SequenceLoopMode.ALWAYS_LOOP);
SequenceUtils.randomStandSequence(instance);
}
private static final class SelectionCircleSize {

View File

@ -31,8 +31,9 @@ public class PathingGrid {
// this blit function is basically copied from HiveWE, maybe remember to mention
// that in credits as well:
// https://github.com/stijnherfst/HiveWE/blob/master/Base/PathingMap.cpp
public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotation,
public void blitPathingOverlayTexture(final float positionX, final float positionY, final int rotationInput,
final BufferedImage pathingTextureTga) {
final int rotation = (rotationInput + 450) % 360;
final int divW = ((rotation % 180) != 0) ? pathingTextureTga.getHeight() : pathingTextureTga.getWidth();
final int divH = ((rotation % 180) != 0) ? pathingTextureTga.getWidth() : pathingTextureTga.getHeight();
for (int j = 0; j < pathingTextureTga.getHeight(); j++) {
@ -65,13 +66,13 @@ public class PathingGrid {
final int rgb = pathingTextureTga.getRGB(i, pathingTextureTga.getHeight() - 1 - j);
byte data = 0;
if ((rgb & 0xFF) > 250) {
if ((rgb & 0xFF) > 127) {
data |= PathingFlags.UNBUILDABLE;
}
if (((rgb & 0xFF00) >> 8) > 250) {
if (((rgb & 0xFF00) >>> 8) > 127) {
data |= PathingFlags.UNFLYABLE;
}
if (((rgb & 0xFF0000) >> 16) > 250) {
if (((rgb & 0xFF0000) >>> 16) > 127) {
data |= PathingFlags.UNWALKABLE;
}
this.dynamicPathingOverlay[(yy * this.pathingGridSizes[0]) + xx] |= data;

View File

@ -383,6 +383,8 @@ public class Terrain {
(this.mapBounds[2] * 128.0f) + this.centerOffset[1],
((this.columns - this.mapBounds[1] - 1) * 128.0f) + this.centerOffset[0],
((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.softwareGroundMesh = new SoftwareGroundMesh(this.groundHeights, this.groundCornerHeights,
this.centerOffset, width, height);
@ -1184,6 +1186,7 @@ public class Terrain {
static Vector3 tmp3 = new Vector3();
private final WaveBuilder waveBuilder;
public PathingGrid pathingGrid;
private final Rectangle shaderMapBoundsRectangle;
/**
* 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?
return this.corners[(int) Math.floor(x)][(int) Math.floor(y)].getBoundary() == 0;
}
public Rectangle getPlayableMapArea() {
return this.shaderMapBoundsRectangle;
}
}

View File

@ -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;
}
}

View File

@ -2,17 +2,19 @@ package com.etheller.warsmash.viewer5.handlers.w3x.rendersim;
import java.util.List;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.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.StandSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
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;
public class RenderAttackProjectile {
public class RenderAttackProjectile implements RenderEffect {
private static final Quaternion pitchHeap = new Quaternion();
private final CAttackProjectile simulationProjectile;
@ -29,6 +31,8 @@ public class RenderAttackProjectile {
private float yaw;
private float pitch;
private boolean done = false;
private float deathTimeElapsed;
public RenderAttackProjectile(final CAttackProjectile simulationProjectile, final MdxComplexInstance modelInstance,
final float z, final float arc, final War3MapViewer war3MapViewer) {
@ -44,25 +48,32 @@ public class RenderAttackProjectile {
final float dyToTarget = targetY - this.y;
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
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.simulationProjectile.getTarget().getFlyHeight()
+ this.simulationProjectile.getTarget().getImpactZ());
+ this.simulationProjectile.getTarget().getFlyHeight() + impactZ);
this.arcPeakHeight = arc * startingDistance;
this.yaw = (float) StrictMath.atan2(dyToTarget, dxToTarget);
}
public boolean updateAnimations(final War3MapViewer war3MapViewer) {
if (this.simulationProjectile.isDone()) {
@Override
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 List<Sequence> sequences = model.getSequences();
final IndexedSequence sequence = StandSequence.selectSequence("death", sequences);
if ((sequence != null) && (this.modelInstance.sequence != sequence.index)) {
final IndexedSequence sequence = SequenceUtils.selectSequence(PrimaryTag.DEATH, SequenceUtils.EMPTY,
sequences, true);
if ((sequence != null) && this.done && !wasDone) {
this.modelInstance.setSequenceLoopMode(SequenceLoopMode.NEVER_LOOP);
this.modelInstance.setSequence(sequence.index);
}
}
else {
if (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1)) {
StandSequence.randomStandSequence(this.modelInstance);
SequenceUtils.randomStandSequence(this.modelInstance);
}
}
final float simX = this.simulationProjectile.getX();
@ -70,13 +81,12 @@ public class RenderAttackProjectile {
final float simDx = simX - this.x;
final float simDy = simY - this.y;
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);
if (simD > 0) {
this.x = this.x + ((speed * simDx) / simD);
this.y = this.y + ((speed * simDy) / simD);
final float targetX = this.simulationProjectile.getTarget().getX();
final float targetY = this.simulationProjectile.getTarget().getY();
final float targetX = this.simulationProjectile.getTargetX();
final float targetY = this.simulationProjectile.getTargetY();
final float dxToTarget = targetX - this.x;
final float dyToTarget = targetY - this.y;
final float d2DToTarget = (float) StrictMath.sqrt((dxToTarget * dxToTarget) + (dyToTarget * dyToTarget));
@ -94,10 +104,16 @@ public class RenderAttackProjectile {
final float arcCurrentHeight = currentHeightPercentage * this.arcPeakHeight;
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;
this.pitch = (float) StrictMath.atan2(slope + d1z, 1);
final float slope = (-2 * (normPeakDist) * this.arcPeakHeight) / halfStartingDistance;
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);
@ -105,7 +121,8 @@ public class RenderAttackProjectile {
this.modelInstance.rotate(pitchHeap.setFromAxisRad(0, -1, 0, this.pitch));
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) {
war3MapViewer.worldScene.removeInstance(this.modelInstance);
}

View File

@ -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);
}

View File

@ -8,6 +8,7 @@ import java.util.Queue;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Quaternion;
import com.etheller.warsmash.parsers.mdlx.Sequence;
import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.util.RenderMathUtils;
@ -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.PrimaryTag;
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.StandSequence;
import com.etheller.warsmash.viewer5.handlers.w3x.UnitSoundset;
import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
@ -40,6 +41,7 @@ public class RenderUnit {
private static final War3ID MODEL_SCALE = War3ID.fromString("usca");
private static final War3ID MOVE_HEIGHT = War3ID.fromString("umvh");
private static final War3ID ORIENTATION_INTERPOLATION = War3ID.fromString("uori");
private static final War3ID ANIM_PROPS = War3ID.fromString("uani");
private static final float[] heapZ = new float[3];
public final MdxComplexInstance instance;
public final MutableGameObject row;
@ -59,11 +61,14 @@ public class RenderUnit {
private boolean swimming;
private final boolean alreadyPlayedDeath = false;
private boolean dead = false;
private final UnitAnimationListenerImpl unitAnimationListenerImpl;
private OrientationInterpolation orientationInterpolation;
private float currentTurnVelocity = 0;
public long lastUnitResponseEndTimeMillis;
private boolean corpse;
private boolean boneCorpse;
public RenderUnit(final War3MapViewer map, final MdxModel model, final MutableGameObject row,
final com.etheller.warsmash.parsers.w3x.unitsdoo.Unit unit, final UnitSoundset soundset,
@ -87,6 +92,16 @@ public class RenderUnit {
instance.setScene(map.worldScene);
this.unitAnimationListenerImpl = new UnitAnimationListenerImpl(instance);
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) {
heapZ[2] = simulationUnit.getFlyHeight();
@ -194,6 +209,31 @@ public class RenderUnit {
this.unitAnimationListenerImpl.removeSecondaryTag(AnimationTokens.SecondaryTag.SWIM);
}
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.instance.moveTo(this.location);
float simulationFacing = this.simulationUnit.getFacing();
@ -266,6 +306,7 @@ public class RenderUnit {
private PrimaryTag currentAnimation;
private EnumSet<SecondaryTag> currentAnimationSecondaryTags;
private float currentSpeedRatio;
private boolean currentlyAllowingRarityVariations;
private final Queue<QueuedAnimation> animationQueue = new LinkedList<>();
public UnitAnimationListenerImpl(final MdxComplexInstance instance) {
@ -274,33 +315,59 @@ public class RenderUnit {
public void addSecondaryTag(final AnimationTokens.SecondaryTag 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) {
this.secondaryAnimationTags.remove(tag);
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio);
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags, this.currentSpeedRatio,
this.currentlyAllowingRarityVariations);
}
@Override
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();
if (force || (animationName != this.currentAnimation)) {
this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags;
this.currentSpeedRatio = speedRatio;
this.currentlyAllowingRarityVariations = allowRarityVariations;
this.recycleSet.clear();
this.recycleSet.addAll(this.secondaryAnimationTags);
this.recycleSet.addAll(secondaryAnimationTags);
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
public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags) {
this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags));
public void queueAnimation(final PrimaryTag animationName, final EnumSet<SecondaryTag> secondaryAnimationTags,
final boolean allowRarityVariations) {
this.animationQueue.add(new QueuedAnimation(animationName, secondaryAnimationTags, allowRarityVariations));
}
public void update() {
@ -310,12 +377,13 @@ public class RenderUnit {
.get(this.instance.sequence).getFlags() == 0)) {
// animation is a looping animation
playAnimation(true, this.currentAnimation, this.currentAnimationSecondaryTags,
this.currentSpeedRatio);
this.currentSpeedRatio, this.currentlyAllowingRarityVariations);
}
else {
final QueuedAnimation nextAnimation = this.animationQueue.poll();
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 final PrimaryTag animationName;
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.secondaryAnimationTags = secondaryAnimationTags;
this.allowRarityVariations = allowRarityVariations;
}
}
}

View File

@ -1,5 +1,10 @@
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 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() {
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;
}
}

View File

@ -1,7 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.util.Arrays;
import com.etheller.warsmash.units.DataTable;
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
@ -9,13 +13,71 @@ import com.etheller.warsmash.units.Element;
*/
public class CGameplayConstants {
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) {
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() {
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;
}
}

View File

@ -1,6 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.util.EnumSet;
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 {
@ -20,4 +24,16 @@ public class CItem extends CWidget {
public float getImpactZ() {
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);
}
}

View File

@ -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;
}
}

View File

@ -1,47 +1,82 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
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.manager.MutableObjectData;
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.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.data.CAbilityData;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CUnitData;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CPathfindingProcessor;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.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 {
private final CUnitData unitData;
private final CAbilityData abilityData;
private final List<CUnit> units;
private final List<CPlayer> players;
private final List<CAttackProjectile> projectiles;
private final List<CAttackProjectile> newProjectiles;
private final HandleIdAllocator handleIdAllocator;
private transient final ProjectileCreator projectileCreator;
private transient final SimulationRenderController simulationRenderController;
private int gameTurnTick = 0;
private final PathingGrid pathingGrid;
private final CWorldCollision worldCollision;
private final CPathfindingProcessor pathfindingProcessor;
private final CGameplayConstants gameplayConstants;
private final Random seededRandom;
public CSimulation(final DataTable miscData, final MutableObjectData parsedUnitData,
final MutableObjectData parsedAbilityData, final ProjectileCreator projectileCreator,
final PathingGrid pathingGrid, final Rectangle entireMapBounds) {
final MutableObjectData parsedAbilityData, final SimulationRenderController simulationRenderController,
final PathingGrid pathingGrid, final Rectangle entireMapBounds, final Random seededRandom,
final List<Player> playerInfos) {
this.gameplayConstants = new CGameplayConstants(miscData);
this.projectileCreator = projectileCreator;
this.simulationRenderController = simulationRenderController;
this.pathingGrid = pathingGrid;
this.unitData = new CUnitData(parsedUnitData);
this.abilityData = new CAbilityData(parsedAbilityData);
this.units = new ArrayList<>();
this.projectiles = new ArrayList<>();
this.newProjectiles = new ArrayList<>();
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.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() {
@ -57,36 +92,48 @@ public class CSimulation {
}
public CUnit createUnit(final War3ID typeId, final int playerIndex, final float x, final float y,
final float facing) {
final CUnit unit = this.unitData.create(this, this.handleIdAllocator.createId(), playerIndex, typeId, x, y,
facing);
final float facing, final BufferedImage buildingPathingPixelMap) {
final CUnit unit = this.unitData.create(this, playerIndex, this.handleIdAllocator.createId(), typeId, x, y,
facing, buildingPathingPixelMap);
this.units.add(unit);
if (!unit.getUnitType().isBuilding()) {
this.worldCollision.addUnit(unit);
}
this.worldCollision.addUnit(unit);
return unit;
}
public CAttackProjectile createProjectile(final CUnit source, final int attackIndex, final CWidget target) {
final CAttackProjectile projectile = this.projectileCreator.create(this, source, attackIndex, target);
this.projectiles.add(projectile);
public CAttackProjectile createProjectile(final CUnit source, final float launchX, final float launchY,
final float launchFacing, final CUnitAttackMissile attack, final CWidget target, final float damage,
final int bounceIndex) {
final CAttackProjectile projectile = this.simulationRenderController.createAttackProjectile(this, launchX,
launchY, launchFacing, source, attack, target, damage, bounceIndex);
this.newProjectiles.add(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() {
return this.pathingGrid;
}
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
final float startY, final Point2D.Float goal, final PathingGrid.MovementType movementType,
final float collisionSize) {
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit, startX, startY, goal,
movementType, collisionSize);
public 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) {
return this.pathfindingProcessor.findNaiveSlowPath(ignoreIntersectionsWithThisUnit,
ignoreIntersectionsWithThisSecondUnit, startX, startY, goal, movementType, collisionSize,
allowSmoothing);
}
public void update() {
for (final CUnit unit : this.units) {
unit.update(this);
final Iterator<CUnit> unitIterator = this.units.iterator();
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();
while (projectileIterator.hasNext()) {
@ -95,6 +142,8 @@ public class CSimulation {
projectileIterator.remove();
}
}
this.projectiles.addAll(this.newProjectiles);
this.newProjectiles.clear();
this.gameTurnTick++;
}
@ -109,4 +158,16 @@ public class CSimulation {
public CGameplayConstants getGameplayConstants() {
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);
}
}

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedList;
@ -8,10 +9,15 @@ import java.util.Queue;
import com.badlogic.gdx.math.Rectangle;
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.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.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 {
private War3ID typeId;
@ -35,12 +41,22 @@ public class CUnit extends CWidget {
private final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
private int deathTurnTick;
private boolean corpse;
private boolean boneCorpse;
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,
final War3ID typeId, final float facing, final float mana, final int maximumLife, final int maximumMana,
final int speed, final int defense, final CUnitType unitType) {
super(handleId, x, y, life);
this.playerIndex = playerIndex;
this.typeId = typeId;
this.facing = facing;
this.mana = mana;
@ -55,7 +71,7 @@ public class CUnit extends CWidget {
public void setUnitAnimationListener(final CUnitAnimationListener 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() {
@ -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) {
if (this.currentOrder != null) {
public boolean update(final CSimulation game) {
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)) {
// remove current order, because it's completed, polling next
// item from order queue
@ -132,12 +186,16 @@ public class CUnit extends CWidget {
}
if (this.currentOrder == null) {
// 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) {
if (isDead()) {
return;
}
if (queue && (this.currentOrder != null)) {
this.orderQueue.add(order);
}
@ -225,13 +283,199 @@ public class CUnit extends CWidget {
return this.defense;
}
public void damage(final CUnit source, final CAttackType attackType, final CWeaponType weaponType,
final int damage) {
}
@Override
public float 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;
}
}

View File

@ -6,11 +6,9 @@ import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag;
public interface CUnitAnimationListener {
EnumSet<SecondaryTag> EMPTY = EnumSet.noneOf(SecondaryTag.class);
EnumSet<SecondaryTag> READY = EnumSet.of(SecondaryTag.READY);
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);
}

View File

@ -0,0 +1,5 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
public interface CUnitEnumFunction {
boolean call(CUnit unit);
}

View File

@ -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();
}
}
}
}

View File

@ -1,11 +1,13 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.awt.image.BufferedImage;
import java.util.EnumSet;
import java.util.List;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CDefenseType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
/**
@ -25,11 +27,18 @@ public class CUnitType {
private final boolean decay;
private final CDefenseType defenseType;
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,
final float defaultFlyingHeight, final float collisionSize,
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.building = isBldg;
this.movementType = movementType;
@ -42,6 +51,9 @@ public class CUnitType {
this.decay = decay;
this.defenseType = defenseType;
this.impactZ = impactZ;
this.buildingPathingPixelMap = buildingPathingPixelMap;
this.deathTime = deathTime;
this.targetedAs = targetedAs;
}
public String getName() {
@ -91,4 +103,16 @@ public class CUnitType {
public float getImpactZ() {
return this.impactZ;
}
public BufferedImage getBuildingPathingPixelMap() {
return this.buildingPathingPixelMap;
}
public float getDeathTime() {
return this.deathTime;
}
public EnumSet<CTargetType> getTargetedAs() {
return this.targetedAs;
}
}

View File

@ -1,10 +1,15 @@
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 {
private final int handleId;
private float x;
private float y;
private float life;
protected float life;
public CWidget(final int handleId, final float x, final float y, final float life) {
this.handleId = handleId;
@ -41,12 +46,17 @@ public abstract class CWidget {
this.life = life;
}
public void damage(final CUnit source, final int damage) {
this.life -= damage;
}
public abstract void damage(final CSimulation simulation, final CUnit source, final CAttackType attackType,
final String weaponType, final float damage);
public abstract float getFlyHeight();
public abstract float getImpactZ();
public boolean isDead() {
return this.life <= 0;
}
public abstract boolean canBeTargetedBy(CSimulation simulation, CUnit source,
final EnumSet<CTargetType> targetsAllowed);
}

View File

@ -1,77 +1,145 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
import java.util.HashSet;
import java.util.Set;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.util.Quadtree;
import com.etheller.warsmash.util.QuadtreeIntersector;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
public class CWorldCollision {
private final Quadtree<CUnit> groundUnitCollision;
private final Quadtree<CUnit> airUnitCollision;
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.airUnitCollision = 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) {
if (unit.getUnitType().isBuilding()) {
throw new IllegalArgumentException("Cannot add building to the CWorldCollision");
}
Rectangle bounds = unit.getCollisionRectangle();
if (bounds == null) {
final float collisionSize = unit.getUnitType().getCollisionSize();
final float collisionSize = Math.min(this.maxCollisionRadius, unit.getUnitType().getCollisionSize());
bounds = new Rectangle(unit.getX() - collisionSize, unit.getY() - collisionSize, collisionSize * 2,
collisionSize * 2);
unit.setCollisionRectangle(bounds);
}
final MovementType movementType = unit.getUnitType().getMovementType();
if (movementType != null) {
switch (movementType) {
case AMPHIBIOUS:
this.seaUnitCollision.add(unit, bounds);
this.groundUnitCollision.add(unit, bounds);
break;
case FLOAT:
this.seaUnitCollision.add(unit, bounds);
break;
case FLY:
this.airUnitCollision.add(unit, bounds);
break;
default:
case DISABLED:
case FOOT:
case HORSE:
case HOVER:
this.groundUnitCollision.add(unit, bounds);
break;
if (unit.getUnitType().isBuilding()) {
// buildings are here so that we can include them when enumerating all units in
// a rect, but they don't really move dynamically, this is kind of pointless
this.buildingUnitCollision.add(unit, bounds);
}
else {
final MovementType movementType = unit.getUnitType().getMovementType();
if (movementType != null) {
switch (movementType) {
case AMPHIBIOUS:
this.seaUnitCollision.add(unit, bounds);
this.groundUnitCollision.add(unit, bounds);
break;
case FLOAT:
this.seaUnitCollision.add(unit, bounds);
break;
case FLY:
this.airUnitCollision.add(unit, bounds);
break;
default:
case DISABLED:
case FOOT:
case HORSE:
case HOVER:
this.groundUnitCollision.add(unit, bounds);
break;
}
}
}
}
public 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,
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) {
switch (movementType) {
case AMPHIBIOUS:
if (this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) {
if (this.seaUnitCollision.intersect(newPossibleRectangle,
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) {
return true;
}
if (this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle)) {
if (this.groundUnitCollision.intersect(newPossibleRectangle,
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore))) {
return true;
}
return false;
case FLOAT:
return this.seaUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
return this.seaUnitCollision.intersect(newPossibleRectangle,
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
case FLY:
return this.airUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
return this.airUnitCollision.intersect(newPossibleRectangle,
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
default:
case DISABLED:
case FOOT:
case HORSE:
case HOVER:
return this.groundUnitCollision.intersectsAnythingOtherThan(sourceUnitToIgnore, newPossibleRectangle);
return this.groundUnitCollision.intersect(newPossibleRectangle,
this.anyUnitExceptTwoIntersector.reset(sourceUnitToIgnore, sourceSecondUnitToIgnore));
}
}
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;
}
}
}

View File

@ -1,11 +1,14 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities;
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.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.StringsToExternalizeLater;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack;
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.AbilityTargetCheckReceiver;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver.TargetType;
@ -47,7 +50,17 @@ public class CAbilityAttack implements CAbility {
@Override
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

View File

@ -10,14 +10,21 @@ public enum CAttackType implements CodeKeyType {
MAGIC,
HERO;
public static CAttackType[] VALUES = values();
private String codeKey;
private String damageKey;
private CAttackType() {
String name = name();
if (name.equals("SPELLS")) {
name = "MAGIC";
final String name = name();
final String computedCodeKey = name.charAt(0) + name.substring(1).toLowerCase();
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
@ -25,6 +32,10 @@ public enum CAttackType implements CodeKeyType {
return this.codeKey;
}
public String getDamageKey() {
return this.damageKey;
}
public static CAttackType parseAttackType(final String attackTypeString) {
return valueOf(attackTypeString.toUpperCase());
}

View File

@ -10,6 +10,8 @@ public enum CDefenseType implements CodeKeyType {
HERO,
DIVINE;
public static CDefenseType[] VALUES = values();
private String codeKey;
private CDefenseType() {

View File

@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
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
* these attacks as best as possible.
*/
public class CUnitAttack {
public abstract class CUnitAttack {
private float animationBackswingPoint;
private float animationDamagePoint;
private CAttackType attackType;
@ -195,4 +198,5 @@ public class CUnitAttack {
return this.maxDamage;
}
public abstract void launch(CSimulation simulation, CUnit unit, CWidget target, float damage);
}

View File

@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
@ -28,4 +31,10 @@ public class CUnitAttackInstant extends CUnitAttack {
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);
}
}

View File

@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
@ -59,4 +62,14 @@ public class CUnitAttackMissile extends CUnitAttack {
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);
}
}

View File

@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
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 {
private float damageLossFactor;
private int maximumNumberOfTargets;
private final int areaOfEffectFullDamage;
private final EnumSet<CTargetType> areaOfEffectTargets;
public CUnitAttackMissileBounce(final float animationBackswingPoint, final float animationDamagePoint,
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 CWeaponType weaponType, final float projectileArc, final String projectileArt,
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,
damageSidesPerDie, damageUpgradeAmount, range, rangeMotionBuffer, showUI, targetsAllowed, weaponSound,
weaponType, projectileArc, projectileArt, projectileHomingEnabled, projectileSpeed);
this.damageLossFactor = damageLossFactor;
this.maximumNumberOfTargets = maximumNumberOfTargets;
this.areaOfEffectFullDamage = areaOfEffectFullDamage;
this.areaOfEffectTargets = areaOfEffectTargets;
}
public float getDamageLossFactor() {
@ -40,4 +50,67 @@ public class CUnitAttackMissileBounce extends CUnitAttackMissile {
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;
}
}
}

View File

@ -2,6 +2,11 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
@ -82,4 +87,66 @@ public class CUnitAttackMissileSplash extends CUnitAttackMissile {
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;
}
}
}

View File

@ -2,6 +2,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks;
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.CTargetType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CWeaponType;
@ -18,4 +21,9 @@ public class CUnitAttackNormal extends CUnitAttack {
weaponType);
}
@Override
public void launch(final CSimulation simulation, final CUnit unit, final CWidget target, final float damage) {
target.damage(simulation, unit, getAttackType(), getWeaponSound(), damage);
}
}

View File

@ -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.CUnit;
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 {
private float x;
private float y;
private final float initialTargetX;
private final float initialTargetY;
private final float speed;
private final CWidget target;
private boolean done;
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,
final int damage) {
final float damage, final CUnitAttackMissile unitAttack, final int bounceIndex) {
this.x = x;
this.y = y;
this.speed = speed;
this.target = target;
this.source = source;
this.damage = damage;
this.unitAttack = unitAttack;
this.bounceIndex = bounceIndex;
this.initialTargetX = target.getX();
this.initialTargetY = target.getY();
}
public boolean update(final CSimulation cSimulation) {
final float tx = this.target.getX();
final float ty = this.target.getY();
final float tx = getTargetX();
final float ty = getTargetY();
final float sx = this.x;
final float sy = this.y;
final float dtsx = tx - sx;
@ -39,7 +49,8 @@ public class CAttackProjectile {
float travelDistance = Math.min(c, this.speed * WarsmashConstants.SIMULATION_STEP_TIME);
if (c <= travelDistance) {
if (!this.done) {
this.target.damage(this.source, this.damage);
this.unitAttack.doDamage(cSimulation, this.source, this.target, this.damage, this.x, this.y,
this.bounceIndex);
}
this.done = true;
travelDistance = c;
@ -73,4 +84,26 @@ public class CAttackProjectile {
public boolean isDone() {
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;
}
}
}

View File

@ -1,5 +1,6 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.data;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
@ -113,6 +114,8 @@ public class CUnitData {
private static final War3ID MOVE_TYPE = War3ID.fromString("umvt");
private static final War3ID COLLISION_SIZE = War3ID.fromString("ucol");
private static final War3ID CLASSIFICATION = War3ID.fromString("utyp");
private static final War3ID DEATH_TIME = War3ID.fromString("udtm");
private static final War3ID TARGETED_AS = War3ID.fromString("utar");
private final MutableObjectData unitData;
private final Map<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,
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 int life = unitType.getFieldAsInteger(HIT_POINT_MAXIMUM, 0);
final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0);
final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0);
final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0);
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
final String unitName = unitType.getFieldAsString(NAME, 0);
final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
if (classificationString != null) {
final String[] classificationValues = classificationString.split(",");
for (final String unitEditorKey : classificationValues) {
final CUnitClassification unitClassification = CUnitClassification
.parseUnitClassification(unitEditorKey);
if (unitClassification != null) {
classifications.add(unitClassification);
final int defense = unitType.getFieldAsInteger(DEFENSE, 0);
CUnitType unitTypeInstance = this.unitIdToUnitType.get(typeId);
if (unitTypeInstance == null) {
final float moveHeight = unitType.getFieldAsFloat(MOVE_HEIGHT, 0);
final String movetp = unitType.getFieldAsString(MOVE_TYPE, 0);
final float collisionSize = unitType.getFieldAsFloat(COLLISION_SIZE, 0);
final boolean isBldg = unitType.getFieldAsBoolean(IS_BLDG, 0);
final PathingGrid.MovementType movementType = PathingGrid.getMovementType(movetp);
final String unitName = unitType.getFieldAsString(NAME, 0);
final EnumSet<CTargetType> targetedAs = CTargetType
.parseTargetTypeSet(unitType.getFieldAsString(TARGETED_AS, 0));
final String classificationString = unitType.getFieldAsString(CLASSIFICATION, 0);
final EnumSet<CUnitClassification> classifications = EnumSet.noneOf(CUnitClassification.class);
if (classificationString != null) {
final String[] classificationValues = classificationString.split(",");
for (final String unitEditorKey : classificationValues) {
final CUnitClassification unitClassification = CUnitClassification
.parseUnitClassification(unitEditorKey);
if (unitClassification != null) {
classifications.add(unitClassification);
}
}
}
final List<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,
speed, defense, new CUnitType(unitName, isBldg, movementType, moveHeight, collisionSize,
classifications, attacks, armorType, raise, decay, defenseType, impactZ));
speed, defense, unitTypeInstance);
if (speed > 0) {
unit.add(simulation, CAbilityMove.INSTANCE);
unit.add(simulation, CAbilityPatrol.INSTANCE);
@ -275,7 +291,8 @@ public class CUnitData {
attack = new CUnitAttackMissileBounce(animationBackswingPoint, animationDamagePoint, attackType,
cooldownTime, damageBase, damageDice, damageSidesPerDie, damageUpgradeAmount, range,
rangeMotionBuffer, showUI, targetsAllowed, weaponSound, weaponType, projectileArc, projectileArt,
projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets);
projectileHomingEnabled, projectileSpeed, damageLossFactor, maximumNumberOfTargets,
areaOfEffectFullDamage, areaOfEffectTargets);
break;
case MSPLASH:
case ARTILLERY:

View File

@ -2,10 +2,10 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitAnimationListener;
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.combat.attacks.CUnitAttack;
@ -15,16 +15,52 @@ public class CAttackOrder implements COrder {
private boolean wasWithinPropWindow = false;
private final CUnitAttack unitAttack;
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) {
this.unit = unit;
this.unitAttack = unitAttack;
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
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 prevY = this.unit.getY();
final float deltaY = this.target.getY() - prevY;
@ -70,10 +106,13 @@ public class CAttackOrder implements COrder {
final int cooldownEndTime = this.unit.getCooldownEndTime();
final int currentTurnTick = simulation.getGameTurnTick();
if (this.wasWithinPropWindow) {
if (this.backswingLaunchTime != 0) {
if (currentTurnTick >= this.backswingLaunchTime) {
simulation.createProjectile(this.unit, 0, this.target);
this.backswingLaunchTime = 0;
if (this.damagePointLaunchTime != 0) {
if (currentTurnTick >= this.damagePointLaunchTime) {
final int minDamage = this.unitAttack.getMinDamage();
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) {
@ -84,15 +123,21 @@ public class CAttackOrder implements COrder {
final int a1DamagePointSteps = (int) (this.unitAttack.getAnimationDamagePoint()
/ WarsmashConstants.SIMULATION_STEP_TIME);
this.unit.setCooldownEndTime(currentTurnTick + a1CooldownSteps);
this.backswingLaunchTime = currentTurnTick + a1DamagePointSteps;
this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK,
CUnitAnimationListener.EMPTY, 1.0f);
this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, CUnitAnimationListener.READY);
this.thisOrderCooldownEndTime = currentTurnTick + a1CooldownSteps;
this.damagePointLaunchTime = currentTurnTick + a1DamagePointSteps;
this.backSwingTime = currentTurnTick + a1DamagePointSteps + a1BackswingSteps;
this.unit.getUnitAnimationListener().playAnimation(true, PrimaryTag.ATTACK, SequenceUtils.EMPTY, 1.0f,
true);
this.unit.getUnitAnimationListener().queueAnimation(PrimaryTag.STAND, SequenceUtils.READY, false);
}
else if ((currentTurnTick >= this.thisOrderCooldownEndTime)) {
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f,
false);
}
}
else {
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, CUnitAnimationListener.READY,
1.0f);
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.READY, 1.0f,
false);
}
return false;

View File

@ -7,12 +7,12 @@ import java.util.List;
import com.badlogic.gdx.math.Rectangle;
import com.etheller.warsmash.util.WarsmashConstants;
import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag;
import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid;
import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.MovementType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.COrder;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit;
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.abilities.CAbilityMove;
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 final CPathfindingProcessor.GridMapping gridMapping;
private final Point2D.Float target;
private int searchCycles = 0;
private CUnit followUnit;
public CMoveOrder(final CUnit unit, final float targetX, final float targetY) {
this.unit = unit;
@ -33,6 +35,15 @@ public class CMoveOrder implements COrder {
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
public boolean update(final CSimulation simulation) {
final float prevX = this.unit.getX();
@ -45,12 +56,15 @@ public class CMoveOrder implements COrder {
final float startFloatingX = prevX;
final float startFloatingY = prevY;
if (this.path == null) {
this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target,
movementType == null ? MovementType.FOOT : movementType, collisionSize);
if (this.followUnit != null) {
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);
// check for smoothing
if (!this.path.isEmpty()) {
this.path.add(this.target);
float lastX = startFloatingX;
float lastY = startFloatingY;
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 currentTargetY;
if (this.path.isEmpty()) {
currentTargetX = this.target.x;
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 {
final Point2D.Float nextPathElement = this.path.get(0);
currentTargetX = nextPathElement.x;
currentTargetY = nextPathElement.y;
if ((this.followUnit != null) && (this.path.size() == 1)) {
currentTargetX = this.followUnit.getX();
currentTargetY = this.followUnit.getY();
}
else {
final Point2D.Float nextPathElement = this.path.get(0);
currentTargetX = nextPathElement.x;
currentTargetY = nextPathElement.y;
}
}
float deltaX = currentTargetX - prevX;
@ -172,24 +210,46 @@ public class CMoveOrder implements COrder {
&& !worldCollision.intersectsAnythingOtherThan(tempRect, this.unit, movementType))) {
this.unit.setPoint(nextX, nextY, worldCollision);
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()) {
return true;
}
else {
System.out.println(this.path);
final Float removed = this.path.remove(0);
System.out.println(
"We think we reached " + removed + " because are at " + nextX + "," + nextY);
if (this.path.isEmpty()) {
currentTargetX = this.target.x;
currentTargetY = this.target.y;
"We think we reached " + removed + " because we are at " + nextX + "," + nextY);
final boolean emptyPath = this.path.isEmpty();
if (emptyPath) {
if (this.followUnit != null) {
currentTargetX = this.followUnit.getX();
currentTargetY = this.followUnit.getY();
}
else {
currentTargetX = this.target.x;
currentTargetY = this.target.y;
}
}
else {
final Point2D.Float firstPathElement = this.path.get(0);
currentTargetX = firstPathElement.x;
currentTargetY = firstPathElement.y;
if ((this.followUnit != null) && (this.path.size() == 1)) {
currentTargetX = this.followUnit.getX();
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 delta: " + deltaX + "," + deltaY);
goalAngleRad = Math.atan2(deltaY, deltaX);
@ -210,7 +270,7 @@ public class CMoveOrder implements COrder {
if (absDelta >= propulsionWindow) {
if (this.wasWithinPropWindow) {
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND,
CUnitAnimationListener.EMPTY, 1.0f);
SequenceUtils.EMPTY, 1.0f, true);
}
this.wasWithinPropWindow = false;
return false;
@ -219,20 +279,21 @@ public class CMoveOrder implements COrder {
}
}
else {
this.path = simulation.findNaiveSlowPath(this.unit, startFloatingX, startFloatingY, this.target,
movementType == null ? MovementType.FOOT : movementType, collisionSize);
if (this.followUnit != null) {
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);
if (this.path.isEmpty()) {
if (this.path.isEmpty() || (this.searchCycles > 5)) {
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;
}
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
// moving.
if (this.wasWithinPropWindow) {
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND,
CUnitAnimationListener.EMPTY, 1.0f);
this.unit.getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f,
true);
}
this.wasWithinPropWindow = false;
}

View File

@ -1,6 +1,7 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing;
import java.awt.geom.Point2D;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
@ -18,7 +19,8 @@ public class CPathfindingProcessor {
private final CWorldCollision worldCollision;
private final Node[][] nodes;
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) {
this.pathingGrid = pathingGrid;
@ -54,12 +56,21 @@ public class CPathfindingProcessor {
*/
public List<Point2D.Float> findNaiveSlowPath(final CUnit ignoreIntersectionsWithThisUnit, final float startX,
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 goalY = goal.y;
if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize)
|| !isPathableDynamically(goalX, goalY, ignoreIntersectionsWithThisUnit, movementType)) {
return Collections.emptyList();
float weightForHittingWalls = 1E9f;
if (!this.pathingGrid.isPathable(goalX, goalY, movementType, collisionSize) || !isPathableDynamically(goalX,
goalY, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType)) {
weightForHittingWalls = 5E2f;
}
System.out.println("beginning findNaiveSlowPath for " + startX + "," + startY + "," + goalX + "," + goalY);
if ((startX == goalX) && (startY == goalY)) {
@ -78,7 +89,20 @@ public class CPathfindingProcessor {
gridMapping = GridMapping.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 startGridX = gridMapping.getX(this.pathingGrid, startX);
for (int i = 0; i < searchGraph.length; i++) {
@ -133,8 +157,8 @@ public class CPathfindingProcessor {
final Node possibleNode = searchGraph[cellY][cellX];
final float x = possibleNode.point.x;
final float y = possibleNode.point.y;
if (pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType, collisionSize, x,
y)) {
if (pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, startX,
startY, movementType, collisionSize, x, y)) {
final double tentativeScore = possibleNode.point.distance(startX, startY);
possibleNode.g = tentativeScore;
@ -142,19 +166,33 @@ public class CPathfindingProcessor {
openSet.add(possibleNode);
}
else {
final double tentativeScore = weightForHittingWalls;
possibleNode.g = tentativeScore;
possibleNode.f = tentativeScore + h(possibleNode);
openSet.add(possibleNode);
}
}
}
}
while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current == this.goal) {
if (isGoal(current)) {
final LinkedList<Point2D.Float> totalPath = new LinkedList<>();
Direction lastCameFromDirection = null;
if ((current.cameFrom != null)
&& pathableBetween(ignoreIntersectionsWithThisUnit, current.cameFrom.point.x,
current.cameFrom.point.y, movementType, collisionSize, goalX, goalY)) {
&& pathableBetween(ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
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,
// skipping the last grid location
totalPath.addFirst(goal);
@ -172,8 +210,16 @@ public class CPathfindingProcessor {
if ((lastCameFromDirection == null) || (current.cameFromDirection != lastCameFromDirection)
|| (current.cameFromDirection == null)) {
if ((current.cameFromDirection != null) || (lastNode == null)
|| !pathableBetween(ignoreIntersectionsWithThisUnit, startX, startY, movementType,
collisionSize, lastNode.point.x, lastNode.point.y)) {
|| !pathableBetween(ignoreIntersectionsWithThisUnit,
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
// the journey by specifically walking to the first one
totalPath.addFirst(current.point);
@ -187,8 +233,7 @@ public class CPathfindingProcessor {
for (final Direction direction : Direction.VALUES) {
final float x = current.point.x + (direction.xOffset * 32);
final float y = current.point.y + (direction.yOffset * 32);
if (this.pathingGrid.contains(x, y) && pathableBetween(ignoreIntersectionsWithThisUnit, current.point.x,
current.point.y, movementType, collisionSize, x, y)) {
if (this.pathingGrid.contains(x, y)) {
double turnCost;
if ((current.cameFromDirection != null) && (direction != current.cameFromDirection)) {
turnCost = 0.25;
@ -196,7 +241,11 @@ public class CPathfindingProcessor {
else {
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
.getX(this.pathingGrid, x)];
if (tentativeScore < neighbor.g) {
@ -214,20 +263,24 @@ public class CPathfindingProcessor {
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) {
return this.pathingGrid.isPathable(x, y, movementType, collisionSize)
&& this.pathingGrid.isPathable(startX, y, movementType, collisionSize)
&& this.pathingGrid.isPathable(x, startY, movementType, collisionSize)
&& isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, movementType)
&& isPathableDynamically(x, startY, ignoreIntersectionsWithThisUnit, movementType)
&& isPathableDynamically(startX, y, ignoreIntersectionsWithThisUnit, movementType);
&& isPathableDynamically(x, y, ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit,
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,
final PathingGrid.MovementType movementType) {
final CUnit ignoreIntersectionsWithThisSecondUnit, final PathingGrid.MovementType movementType) {
return !this.worldCollision.intersectsAnythingOtherThan(tempRect.setCenter(x, y),
ignoreIntersectionsWithThisUnit, movementType);
ignoreIntersectionsWithThisUnit, ignoreIntersectionsWithThisSecondUnit, movementType);
}
public static boolean isCollisionSizeBetterSuitedForCorners(final float collisionSize) {
@ -242,8 +295,24 @@ public class CPathfindingProcessor {
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) {
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 {

View File

@ -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;
}

View File

@ -0,0 +1,10 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
public enum CMapControl {
USER,
COMPUTER,
RESCUABLE,
NEUTRAL,
CREEP,
NONE;
}

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation;
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
public interface CPlayerController {
boolean issueTargetOrder(int unitHandleId, int orderId, int targetHandleId);

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players;
public enum CRacePreference {
HUMAN,
ORC,
NIGHTELF,
UNDEAD,
DEMON,
RANDOM,
USER_SELECTABLE;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -2,6 +2,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.ui;
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.g2d.BitmapFont;
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.jass.Jass2.RootFrameListener;
import com.etheller.warsmash.util.FastNumberFormat;
import com.etheller.warsmash.util.ImageUtils;
import com.etheller.warsmash.viewer5.Scene;
import com.etheller.warsmash.viewer5.handlers.mdx.MdxComplexInstance;
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.rendersim.CommandCardIcon;
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.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.CDefenseType;
import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CodeKeyType;
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 Viewport uiViewport;
private final FreeTypeFontGenerator fontGenerator;
@ -43,6 +51,10 @@ public class MeleeUI {
private GameUI rootFrame;
private UIFrame consoleUI;
private UIFrame resourceBar;
private StringFrame resourceBarGoldText;
private StringFrame resourceBarLumberText;
private StringFrame resourceBarSupplyText;
private StringFrame resourceBarUpkeepText;
private UIFrame timeIndicator;
private UIFrame unitPortrait;
private StringFrame unitLifeText;
@ -69,6 +81,10 @@ public class MeleeUI {
private StringFrame armorInfoPanelIconLevel;
private InfoPanelIconBackdrops damageBackdrops;
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,
final Scene uiScene, final War3MapViewer war3MapViewer, final RootFrameListener rootFrameListener) {
@ -79,6 +95,8 @@ public class MeleeUI {
this.war3MapViewer = war3MapViewer;
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
// =================================
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);
try {
this.rootFrame.loadTOCFile("UI\\FrameDef\\FrameDef.toc");
@ -118,6 +136,16 @@ public class MeleeUI {
// put it in the "TOPRIGHT" corner.
this.resourceBar = this.rootFrame.createSimpleFrame("ResourceBarFrame", this.consoleUI, 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)
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) {
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() {
@ -196,12 +243,12 @@ public class MeleeUI {
this.portraitCameraManager.updateCamera();
if ((this.modelInstance != null)
&& (this.modelInstance.sequenceEnded || (this.modelInstance.sequence == -1))) {
StandSequence.randomPortraitSequence(this.modelInstance);
SequenceUtils.randomPortraitSequence(this.modelInstance);
}
}
public void talk() {
StandSequence.randomPortraitTalkSequence(this.modelInstance);
SequenceUtils.randomPortraitTalkSequence(this.modelInstance);
}
public void setSelectedUnit(final RenderUnit unit) {
@ -220,7 +267,7 @@ public class MeleeUI {
}
this.modelInstance = (MdxComplexInstance) portraitModel.addInstance();
this.portraitCameraManager.setModelInstance(this.modelInstance, portraitModel);
this.modelInstance.setSequenceLoopMode(1);
this.modelInstance.setSequenceLoopMode(SequenceLoopMode.MODEL_LOOP);
this.modelInstance.setScene(this.portraitScene);
this.modelInstance.setVertexColor(unit.instance.vertexColor);
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.selectedUnit = unit;
if (unit == null) {
this.simpleNameValue.setText("");
this.unitLifeText.setText("");
@ -245,6 +299,7 @@ public class MeleeUI {
this.armorInfoPanelIconLevel.setText("");
}
else {
unit.getSimulationUnit().addStateListener(this);
this.simpleNameValue.setText(unit.getSimulationUnit().getUnitType().getName());
String classText = null;
for (final CUnitClassification classification : unit.getSimulationUnit().getClassifications()) {
@ -291,8 +346,13 @@ public class MeleeUI {
}
this.armorIcon.setVisible(true);
this.armorIconBackdrop.setTexture(
this.defenseBackdrops.getTexture(unit.getSimulationUnit().getUnitType().getDefenseType()));
final Texture defenseTexture = this.defenseBackdrops
.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()));
}
}
@ -309,8 +369,8 @@ public class MeleeUI {
this.uiViewport.project(this.projectionTemp1);
this.uiViewport.project(this.projectionTemp2);
this.tempRect.x = this.projectionTemp1.x;
this.tempRect.y = this.projectionTemp1.y;
this.tempRect.x = this.projectionTemp1.x + this.uiViewport.getScreenX();
this.tempRect.y = this.projectionTemp1.y + this.uiViewport.getScreenY();
this.tempRect.width = this.projectionTemp2.x - this.projectionTemp1.x;
this.tempRect.height = this.projectionTemp2.y - this.projectionTemp1.y;
this.portrait.portraitScene.camera.viewport(this.tempRect);
@ -325,10 +385,11 @@ public class MeleeUI {
for (int index = 0; index < attackTypes.length; index++) {
final CodeKeyType attackType = attackTypes[index];
String skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey() + suffix;
try {
this.damageBackdropTextures[index] = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
final Texture suffixTexture = gameUI.loadTexture(gameUI.getSkinField(skinLookupKey));
if (suffixTexture != null) {
this.damageBackdropTextures[index] = suffixTexture;
}
catch (final Exception exc) {
else {
skinLookupKey = "InfoPanelIcon" + prefix + attackType.getCodeKey();
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;
}
}

View File

@ -84,8 +84,14 @@ public class DesktopLauncher {
config.gles30ContextMajorVersion = 3;
config.gles30ContextMinorVersion = 3;
config.samples = 16;
config.fullscreen = false;
if (config.fullscreen) {
config.vSyncEnabled = false;
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();
config.width = desktopDisplayMode.width;
config.height = desktopDisplayMode.height;