diff --git a/build.gradle b/build.gradle index 9d2880b..6a7b466 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,15 @@ allprojects { } } +project(":server") { + apply plugin: "java" + + + dependencies { + compile project(":shared") + } +} + project(":desktop") { apply plugin: "java" @@ -62,6 +71,7 @@ project(":core") { dependencies { + compile project(":shared") compile project(":fdfparser") compile project(":jassparser") compile "com.badlogicgames.gdx:gdx:$gdxVersion" @@ -75,6 +85,14 @@ project(":core") { } } +project(":shared") { + apply plugin: "java" + + + dependencies { + } +} + project(":fdfparser") { apply plugin: "antlr" diff --git a/core/src/com/etheller/warsmash/SingleModelScreen.java b/core/src/com/etheller/warsmash/SingleModelScreen.java index cf583e1..66a9d1c 100644 --- a/core/src/com/etheller/warsmash/SingleModelScreen.java +++ b/core/src/com/etheller/warsmash/SingleModelScreen.java @@ -2,4 +2,8 @@ package com.etheller.warsmash; public interface SingleModelScreen { void setModel(String path); + + void alternateModelToBattlenet(); + + void unAlternateModelBackToNormal(); } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxFDFTestRenderScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxFDFTestRenderScreen.java index 46700a0..45fdf21 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxFDFTestRenderScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxFDFTestRenderScreen.java @@ -865,4 +865,16 @@ public class WarsmashGdxFDFTestRenderScreen implements InputProcessor, Screen, S @Override public void resume() { } + + @Override + public void alternateModelToBattlenet() { + System.err.println( + "alternateModelToBattlenet() on background not supported for FDF test render screen... TODO write better code here"); + } + + @Override + public void unAlternateModelBackToNormal() { + System.err.println( + "alternateModelToBattlenet() on background not supported for FDF test render screen... TODO write better code here"); + } } diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java index db5050f..8033840 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMapScreen.java @@ -193,7 +193,7 @@ public class WarsmashGdxMapScreen implements InputProcessor, Screen { this.viewer.getCommandErrorListener().setDelegate(this.meleeUI); final ModelInstance libgdxContentInstance = new LibGDXContentLayerModel(null, this.viewer, "", this.viewer.mapPathSolver, "").addInstance(); - libgdxContentInstance.setLocation(0f, 0f, 0.5f); + libgdxContentInstance.setLocation(0f, 0f, -0.5f); libgdxContentInstance.setScene(this.uiScene); this.meleeUI.main(); diff --git a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java index d22aaca..08cd05f 100644 --- a/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java +++ b/core/src/com/etheller/warsmash/WarsmashGdxMenuScreen.java @@ -5,11 +5,11 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; +import java.util.EnumSet; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.InputProcessor; import com.badlogic.gdx.Screen; -import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.GL30; import com.badlogic.gdx.graphics.OrthographicCamera; @@ -27,7 +27,6 @@ import com.etheller.warsmash.datasources.DataSource; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.jass.Jass2.RootFrameListener; import com.etheller.warsmash.units.DataTable; -import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.util.WarsmashConstants; @@ -48,6 +47,8 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; import com.etheller.warsmash.viewer5.handlers.mdx.Sequence; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; +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.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.ui.MenuUI; @@ -71,9 +72,9 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode private Texture solidGreenTexture; private MenuUI menuUI; private final WarsmashGdxMultiScreenGame game; - private Music currentMusic; private boolean hasPlayedStandHack = false; private boolean loaded = false; + private EnumSet tags = SequenceUtils.EMPTY; public WarsmashGdxMenuScreen(final DataTable warsmashIni, final WarsmashGdxMultiScreenGame game) { this.warsmashIni = warsmashIni; @@ -196,25 +197,6 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode public void onCreate(final GameUI rootFrame) { // WarsmashGdxMapGame.this.viewer.setGameUI(rootFrame); - if (WarsmashConstants.ENABLE_MUSIC) { - final String musicField = rootFrame - .getSkinField("GlueMusic_V" + WarsmashConstants.GAME_VERSION); - final String[] musics = musicField.split(";"); - String musicPath = musics[(int) (Math.random() * musics.length)]; - if (musicSLK.get(musicPath) != null) { - musicPath = musicSLK.get(musicPath).getField("FileNames"); - } - final String[] moreSplitMusics = musicPath.split(","); - final String finalMusicPath = moreSplitMusics[(int) (Math.random() - * moreSplitMusics.length)]; - final Music music = Gdx.audio.newMusic(new DataSourceFileHandle( - WarsmashGdxMenuScreen.this.viewer.dataSource, finalMusicPath)); -// music.setVolume(0.2f); - music.setLooping(true); - music.play(); - WarsmashGdxMenuScreen.this.currentMusic = music; - } - singleModelScene(WarsmashGdxMenuScreen.this.scene, War3MapViewer.mdx(rootFrame .getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)), "Stand"); @@ -242,9 +224,7 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode } Gdx.input.setInputProcessor(this); - if (this.currentMusic != null) { - this.currentMusic.play(); - } + this.menuUI.show(); } @@ -369,6 +349,24 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode } + @Override + public void alternateModelToBattlenet() { + if (this.mainInstance != null) { + SequenceUtils.randomSequence(this.mainInstance, PrimaryTag.STAND, SequenceUtils.ALTERNATE, true); + this.tags = SequenceUtils.ALTERNATE; + this.hasPlayedStandHack = false; + } + } + + @Override + public void unAlternateModelBackToNormal() { + if (this.mainInstance != null) { + SequenceUtils.randomSequence(this.mainInstance, PrimaryTag.MORPH, SequenceUtils.ALTERNATE, true); + this.tags = SequenceUtils.EMPTY; + this.hasPlayedStandHack = false; + } + } + private void acolytesHarvestingScene(final Scene scene) { final MdxModel acolyteModel = (MdxModel) this.viewer.load("units\\undead\\acolyte\\acolyte.mdx", @@ -569,7 +567,7 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode if ((this.mainInstance != null) && this.mainInstance.sequenceEnded && (((this.mainModel.getSequences().get(this.mainInstance.sequence).getFlags() & 0x1) == 0) || !this.hasPlayedStandHack)) { - SequenceUtils.randomStandSequence(this.mainInstance); + SequenceUtils.randomSequence(this.mainInstance, PrimaryTag.STAND, this.tags, true); this.hasPlayedStandHack = true; } this.viewer.updateAndRender(); @@ -784,6 +782,7 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode private void renderLibGDXContent() { + Gdx.gl30.glClear(GL30.GL_DEPTH_BUFFER_BIT); Gdx.gl30.glDisable(GL30.GL_SCISSOR_TEST); Gdx.gl30.glDisable(GL30.GL_CULL_FACE); @@ -889,9 +888,6 @@ public class WarsmashGdxMenuScreen implements InputProcessor, Screen, SingleMode @Override public void hide() { - if (this.currentMusic != null) { - this.currentMusic.stop(); - } this.menuUI.hide(); } diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClient.java b/core/src/com/etheller/warsmash/networking/WarsmashClient.java index 6e2ce55..9ef7851 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClient.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClient.java @@ -9,11 +9,12 @@ import java.util.Map; import java.util.Queue; import com.badlogic.gdx.Gdx; -import com.etheller.warsmash.networking.udp.OrderedUdpClient; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; +import net.warsmash.networking.udp.OrderedUdpClient; + public class WarsmashClient implements ServerToClientListener, GameTurnManager { private final OrderedUdpClient udpClient; private final War3MapViewer game; diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClientParser.java b/core/src/com/etheller/warsmash/networking/WarsmashClientParser.java index b55d7eb..383ea84 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClientParser.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClientParser.java @@ -2,7 +2,7 @@ package com.etheller.warsmash.networking; import java.nio.ByteBuffer; -import com.etheller.warsmash.networking.udp.OrderedUdpClientListener; +import net.warsmash.networking.udp.OrderedUdpClientListener; public class WarsmashClientParser implements OrderedUdpClientListener { private final ServerToClientListener listener; diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java b/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java index 4729e6d..efc5f65 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClientWriter.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import com.etheller.warsmash.networking.udp.OrderedUdpClient; +import net.warsmash.networking.udp.OrderedUdpClient; public class WarsmashClientWriter { private final OrderedUdpClient client; diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServer.java b/core/src/com/etheller/warsmash/networking/WarsmashServer.java index b740a18..a8f35f7 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServer.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServer.java @@ -8,9 +8,10 @@ import java.util.List; import java.util.Map; import java.util.Scanner; -import com.etheller.warsmash.networking.udp.OrderedUdpServer; import com.etheller.warsmash.util.WarsmashConstants; +import net.warsmash.networking.udp.OrderedUdpServer; + public class WarsmashServer implements ClientToServerListener { private static final int MAGIC_DELAY_OFFSET = 4; private final OrderedUdpServer udpServer; diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java b/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java index 320ce1d..982f00b 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServerParser.java @@ -4,7 +4,7 @@ import java.io.IOException; import java.net.SocketAddress; import java.nio.ByteBuffer; -import com.etheller.warsmash.networking.udp.OrderedUdpServerListener; +import net.warsmash.networking.udp.OrderedUdpServerListener; public class WarsmashServerParser implements OrderedUdpServerListener { diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java b/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java index 2045c33..28cbc62 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServerWriter.java @@ -6,7 +6,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Set; -import com.etheller.warsmash.networking.udp.OrderedUdpServer; +import net.warsmash.networking.udp.OrderedUdpServer; public class WarsmashServerWriter implements ServerToClientListener { private final OrderedUdpServer server; diff --git a/core/src/com/etheller/warsmash/networking/uberserver/GamingNetworkServerClientBuilder.java b/core/src/com/etheller/warsmash/networking/uberserver/GamingNetworkServerClientBuilder.java new file mode 100644 index 0000000..d931797 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/uberserver/GamingNetworkServerClientBuilder.java @@ -0,0 +1,8 @@ +package com.etheller.warsmash.networking.uberserver; + +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.uberserver.GamingNetworkServerToClientListener; + +public interface GamingNetworkServerClientBuilder { + GamingNetworkServerToClientListener createClient(WritableOutput output); +} diff --git a/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServer.java b/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServer.java new file mode 100644 index 0000000..79fbc52 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServer.java @@ -0,0 +1,52 @@ +package com.etheller.warsmash.networking.uberserver; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import net.warsmash.nio.channels.ChannelOpener; +import net.warsmash.nio.channels.SocketChannelCallback; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; +import net.warsmash.uberserver.GamingNetwork; + +public class TCPGamingNetworkServer { + private final ChannelOpener channelOpener; + + public TCPGamingNetworkServer(final ChannelOpener channelOpener) { + this.channelOpener = channelOpener; + } + + public void start() { + this.channelOpener.openTCPServerChannel(GamingNetwork.PORT, new SocketChannelCallback() { + @Override + public TCPClientParser onConnect(final WritableOutput writableOpenedChannel, + final SocketAddress remoteAddress) { + System.out.println("Received connection from " + remoteAddress); + return new TCPClientParser() { + @Override + public void parse(final ByteBuffer data) { + System.out.println("Got " + data.remaining() + " bytes from " + remoteAddress); + if (data.hasRemaining()) { + System.out.print("["); + System.out.print(data.get()); + while (data.hasRemaining()) { + System.out.print(", "); + System.out.print(data.get()); + } + System.out.println("]"); + } + writableOpenedChannel.write(ByteBuffer.wrap("reply".getBytes())); + } + + @Override + public void disconnected() { + System.out.println("Disconnected from " + remoteAddress); + } + }; + } + }, ExceptionListener.THROW_RUNTIME, 8 * 1024 * 1024, ByteOrder.BIG_ENDIAN); + } + +} diff --git a/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServerClientParser.java b/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServerClientParser.java new file mode 100644 index 0000000..fb7f915 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/uberserver/TCPGamingNetworkServerClientParser.java @@ -0,0 +1,76 @@ +package com.etheller.warsmash.networking.uberserver; + +import java.nio.ByteBuffer; + +import com.etheller.warsmash.util.War3ID; + +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.uberserver.GamingNetworkClientToServerListener; + +public class TCPGamingNetworkServerClientParser implements TCPClientParser { + private final GamingNetworkClientToServerListener listener; + + public TCPGamingNetworkServerClientParser(final GamingNetworkClientToServerListener listener) { + this.listener = listener; + } + + @Override + public void parse(final ByteBuffer data) { + while (data.remaining() > 8) { + final int protocolMessageId = data.getInt(data.position() + 0); + final int length = data.getInt(data.position() + 4); + if (data.remaining() >= length) { + data.position(data.position() + 8); + switch (protocolMessageId) { + case GamingNetworkClientToServerListener.Protocol.HANDSHAKE: { + final int gameIdInt = data.getInt(); + final int gameVersion = data.getInt(); + this.listener.handshake(new War3ID(gameIdInt).toString(), gameVersion); + break; + } + case GamingNetworkClientToServerListener.Protocol.CREATE_ACCOUNT: { + final String username = readString(64, data); + final String password = readString(1024, data); + this.listener.createAccount(username, password); + break; + } + case GamingNetworkClientToServerListener.Protocol.LOGIN: { + final String username = readString(64, data); + final String password = readString(1024, data); + this.listener.login(username, password); + break; + } + case GamingNetworkClientToServerListener.Protocol.JOIN_CHANNEL: { + final String channelName = readString(256, data); + this.listener.joinChannel(channelName); + break; + } + case GamingNetworkClientToServerListener.Protocol.CHAT_MESSAGE: { + final String text = readString(256, data); + this.listener.chatMessage(text); + break; + } + case GamingNetworkClientToServerListener.Protocol.EMOTE_MESSAGE: { + final String text = readString(256, data); + this.listener.emoteMessage(text); + break; + } + } + } + } + } + + public String readString(final int maxLength, final ByteBuffer data) { + final int usernameStringLength = Math.min(maxLength, data.getInt()); + final byte[] usernameStringBytes = new byte[usernameStringLength]; + data.get(usernameStringBytes); + final String username = new String(usernameStringBytes); + return username; + } + + @Override + public void disconnected() { + this.listener.disconnected(); + } + +} diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpClientTestMain.java b/core/src/com/etheller/warsmash/networking/udp/UdpClientTestMain.java new file mode 100644 index 0000000..1576826 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/udp/UdpClientTestMain.java @@ -0,0 +1,55 @@ +package com.etheller.warsmash.networking.udp; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; + +import com.etheller.warsmash.util.WarsmashConstants; + +import net.warsmash.networking.udp.UdpClient; +import net.warsmash.networking.udp.UdpClientListener; + +public class UdpClientTestMain { + + static UdpClient warsmashUdpClient; + + public static void main(final String[] args) { + try { + warsmashUdpClient = new UdpClient(InetAddress.getLocalHost(), WarsmashConstants.PORT_NUMBER, + new UdpClientListener() { + @Override + public void parse(final ByteBuffer buffer) { + System.out.println("got " + buffer.remaining() + " bytes, pos=" + buffer.position() + + ", lim=" + buffer.limit()); + System.out.println("got from server: " + buffer.getInt()); + } + }); + new Thread(warsmashUdpClient).start(); + + final ByteBuffer sendBuffer = ByteBuffer.allocate(1024); + for (int i = 0; i < 10; i++) { + sendBuffer.clear(); + sendBuffer.put((byte) (1 + i)); + sendBuffer.put((byte) 3); + sendBuffer.put((byte) 5); + sendBuffer.put((byte) 7); + sendBuffer.flip(); + warsmashUdpClient.send(sendBuffer); + try { + Thread.sleep(1000); + } + catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + catch (final UnknownHostException e) { + e.printStackTrace(); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpServerTestMain.java b/core/src/com/etheller/warsmash/networking/udp/UdpServerTestMain.java new file mode 100644 index 0000000..a8ba1f3 --- /dev/null +++ b/core/src/com/etheller/warsmash/networking/udp/UdpServerTestMain.java @@ -0,0 +1,46 @@ +package com.etheller.warsmash.networking.udp; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; + +import com.etheller.warsmash.util.WarsmashConstants; + +import net.warsmash.networking.udp.UdpServer; +import net.warsmash.networking.udp.UdpServerListener; + +public class UdpServerTestMain { + + static UdpServer warsmashGameServer; + + public static void main(final String[] args) { + try { + warsmashGameServer = new UdpServer(WarsmashConstants.PORT_NUMBER, new UdpServerListener() { + int n = 0; + ByteBuffer sendBuffer = ByteBuffer.allocate(1024); + + @Override + public void parse(final SocketAddress sourceAddress, final ByteBuffer buffer) { + System.out.println("Got packet from: " + sourceAddress); + while (buffer.hasRemaining()) { + System.out.println("Received: " + buffer.get()); + } + try { + this.sendBuffer.clear(); + this.sendBuffer.putInt(this.n++); + this.sendBuffer.flip(); + warsmashGameServer.send(sourceAddress, this.sendBuffer); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + }); + new Thread(warsmashGameServer).start(); + } + catch (final IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java index ac33d77..88bd3fa 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/GameUI.java @@ -27,6 +27,7 @@ import com.etheller.warsmash.parsers.fdf.datamodel.AnchorDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.BackdropCornerFlags; import com.etheller.warsmash.parsers.fdf.datamodel.ControlStyle; import com.etheller.warsmash.parsers.fdf.datamodel.FontDefinition; +import com.etheller.warsmash.parsers.fdf.datamodel.FontFlags; import com.etheller.warsmash.parsers.fdf.datamodel.FrameClass; import com.etheller.warsmash.parsers.fdf.datamodel.FrameDefinition; import com.etheller.warsmash.parsers.fdf.datamodel.FramePoint; @@ -65,6 +66,7 @@ import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.custom.WTS; import com.etheller.warsmash.util.ImageUtils; import com.etheller.warsmash.util.StringBundle; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.AbstractMdxModelViewer; import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; @@ -235,19 +237,37 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } public String getSkinField(String file) { - if ((file != null) && this.skin.hasField(file)) { + if (file == null) { + throw new NullPointerException("file is null"); + } + if (this.skin.hasField(file)) { file = this.skin.getField(file); } else { - throw new IllegalStateException("Decorated file name lookup not available: " + file); + final String fieldVersioned = file + "_V" + WarsmashConstants.GAME_VERSION; + if (this.skin.hasField(fieldVersioned)) { + file = this.skin.getField(fieldVersioned); + } + else { + throw new IllegalStateException("Decorated file name lookup not available: " + file); + } } return file; } public String trySkinField(String file) { - if ((file != null) && this.skin.hasField(file)) { + if (file == null) { + throw new NullPointerException("file is null"); + } + if (this.skin.hasField(file)) { file = this.skin.getField(file); } + else { + final String fieldVersioned = file + "_V" + WarsmashConstants.GAME_VERSION; + if (this.skin.hasField(fieldVersioned)) { + file = this.skin.getField(fieldVersioned); + } + } return file; } @@ -447,6 +467,12 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } } + String fontFlagsString = frameDefinition.getString("FontFlags"); + if (fontFlagsString == null) { + fontFlagsString = ""; + } + final EnumSet fontFlags = FontFlags.parseFontFlags(fontFlagsString); + Color fontColor; final Vector4Definition fontColorDefinition = frameDefinition.getVector4("FontColor"); if (fontColorDefinition == null) { @@ -511,6 +537,9 @@ public final class GameUI extends AbstractUIFrame implements UIFrame { } final StringFrame stringFrame = new StringFrame(frameDefinition.getName(), parent, fontColor, justifyH, justifyV, frameFont, textString, fontHighlightColor, fontDisabledColor); + if (fontFlags.contains(FontFlags.PASSWORDFIELD)) { + stringFrame.setPasswordField(true); + } if (fontShadowColor != null) { final Vector2Definition shadowOffset = frameDefinition.getVector2("FontShadowOffset"); stringFrame.setFontShadowColor(fontShadowColor); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java index 418840f..32b30ac 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/AbstractRenderableFrame.java @@ -358,6 +358,11 @@ public abstract class AbstractRenderableFrame implements UIFrame { return this.parent; } + @Override + public void setParent(final UIFrame parent) { + this.parent = parent; + } + @Override public boolean isVisibleOnScreen() { boolean visibleOnScreen = this.visible; diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java index db1fbcd..4870953 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/EditBoxFrame.java @@ -56,9 +56,10 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr if ((time % 500) > 250) { final BitmapFont frameFont = this.editTextFrame.getFrameFont(); frameFont.setColor(this.editCursorColor); - final int cursorRenderPosition = Math.min(this.cursorIndex, this.editTextFrame.getText().length()); + final int cursorRenderPosition = Math.min(this.cursorIndex, + this.editTextFrame.getDisplayText().length()); this.cursorIndex = cursorRenderPosition; - glyphLayout.setText(frameFont, this.editTextFrame.getText().substring(0, cursorRenderPosition)); + glyphLayout.setText(frameFont, this.editTextFrame.getDisplayText().substring(0, cursorRenderPosition)); final float cursorXOffset = glyphLayout.width; glyphLayout.setText(frameFont, "|"); frameFont.draw(batch, "|", @@ -125,7 +126,7 @@ public class EditBoxFrame extends AbstractRenderableFrame implements FocusableFr @Override public boolean keyTyped(final char character) { - if (Character.isAlphabetic(character) || Character.isDigit(character)) { + if (Character.isAlphabetic(character) || Character.isDigit(character) || (character == ' ')) { final String prevText = this.editTextFrame.getText(); final int prevTextLength = prevText.length(); final int cursorIndex = Math.min(this.cursorIndex, prevTextLength); diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java index 6d30823..fb88ee0 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/StringFrame.java @@ -19,6 +19,7 @@ public class StringFrame extends AbstractRenderableFrame { private final List internalFrames = new ArrayList<>(); private Color color; private String text = "Default string"; + private String displayText = this.text; private final TextJustify justifyH; private TextJustify justifyV; private final BitmapFont frameFont; @@ -34,6 +35,7 @@ public class StringFrame extends AbstractRenderableFrame { private final Color fontHighlightColor; private final Color fontDisabledColor; private final Color fontColor; + private boolean passwordField; public StringFrame(final String name, final UIFrame parent, final Color color, final TextJustify justifyH, final TextJustify justifyV, final BitmapFont frameFont, final String text, final Color fontHighlightColor, @@ -45,6 +47,7 @@ public class StringFrame extends AbstractRenderableFrame { this.justifyV = justifyV; this.frameFont = frameFont; this.text = text; + this.displayText = text; this.fontHighlightColor = fontHighlightColor; this.fontDisabledColor = fontDisabledColor; this.internalFramesContainer = new SimpleFrame(null, this); @@ -58,11 +61,25 @@ public class StringFrame extends AbstractRenderableFrame { return this.text; } + public String getDisplayText() { + return this.displayText; + } + public void setText(final String text, final GameUI gameUI, final Viewport viewport) { if (text == null) { throw new IllegalArgumentException(); } this.text = text; + if (this.passwordField) { + final StringBuilder displayTextBuilder = new StringBuilder(); + for (int i = 0; i < text.length(); i++) { + displayTextBuilder.append('*'); + } + this.displayText = displayTextBuilder.toString(); + } + else { + this.displayText = text; + } positionBounds(gameUI, viewport); } @@ -162,20 +179,21 @@ public class StringFrame extends AbstractRenderableFrame { final float startingBoundsWidth = getAssignedWidth(); final boolean firstInLine = false; Color currentColor = this.color; - for (int i = 0; i < this.text.length(); i++) { - final char c = this.text.charAt(i); + final String displayTextToUse = this.displayText; + for (int i = 0; i < displayTextToUse.length(); i++) { + final char c = displayTextToUse.charAt(i); switch (c) { case '|': { // special control character - if ((i + 1) < this.text.length()) { - final char escapedCharacter = this.text.charAt(i + 1); + if ((i + 1) < displayTextToUse.length()) { + final char escapedCharacter = displayTextToUse.charAt(i + 1); switch (escapedCharacter) { case 'c': case 'C': - if ((i + 9) < this.text.length()) { + if ((i + 9) < displayTextToUse.length()) { int colorInt; try { - final String upperCase = this.text.substring(i + 2, i + 10).toUpperCase(); + final String upperCase = displayTextToUse.substring(i + 2, i + 10).toUpperCase(); colorInt = (int) Long.parseLong(upperCase, 16); } catch (final NumberFormatException exc) { @@ -507,4 +525,8 @@ public class StringFrame extends AbstractRenderableFrame { public BitmapFont getFrameFont() { return this.frameFont; } + + public void setPasswordField(final boolean passwordField) { + this.passwordField = passwordField; + } } diff --git a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java index 3bd83b1..0a4d246 100644 --- a/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java +++ b/core/src/com/etheller/warsmash/parsers/fdf/frames/UIFrame.java @@ -41,6 +41,8 @@ public interface UIFrame { UIFrame getParent(); + void setParent(UIFrame parent); + boolean isVisible(); boolean isVisibleOnScreen(); diff --git a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java index 11f7cc5..ebcb381 100644 --- a/core/src/com/etheller/warsmash/parsers/jass/Jass2.java +++ b/core/src/com/etheller/warsmash/parsers/jass/Jass2.java @@ -2342,22 +2342,7 @@ public class Jass2 { final boolean random = arguments.get(1).visit(BooleanJassValueVisitor.getInstance()); final int index = arguments.get(2).visit(IntegerJassValueVisitor.getInstance()); - String musicField; - if (!CommonEnvironment.this.gameUI.hasSkinField(musicName)) { - // TODO this versioning system should probably be more general case than this, - // maybe at the level - // of skin field lookup??? - final String versionedMusic = "Music_V" + WarsmashConstants.GAME_VERSION; - if (!CommonEnvironment.this.gameUI.hasSkinField(versionedMusic)) { - musicField = musicName; - } - else { - musicField = CommonEnvironment.this.gameUI.getSkinField(versionedMusic); - } - } - else { - musicField = CommonEnvironment.this.gameUI.getSkinField(musicName); - } + final String musicField = CommonEnvironment.this.gameUI.trySkinField(musicName); meleeUI.playMusic(musicField, random, index); return null; } @@ -2368,22 +2353,7 @@ public class Jass2 { final TriggerExecutionScope triggerScope) { final String musicName = arguments.get(0).visit(StringJassValueVisitor.getInstance()); - String musicField; - if (!CommonEnvironment.this.gameUI.hasSkinField(musicName)) { - // TODO this versioning system should probably be more general case than this, - // maybe at the level - // of skin field lookup??? - final String versionedMusic = "Music_V" + WarsmashConstants.GAME_VERSION; - if (!CommonEnvironment.this.gameUI.hasSkinField(versionedMusic)) { - musicField = musicName; - } - else { - musicField = CommonEnvironment.this.gameUI.getSkinField(versionedMusic); - } - } - else { - musicField = CommonEnvironment.this.gameUI.getSkinField(musicName); - } + final String musicField = CommonEnvironment.this.gameUI.trySkinField(musicName); meleeUI.playMusic(musicField, true, 0); return null; } @@ -2489,8 +2459,9 @@ public class Jass2 { final double x = arguments.get(2).visit(RealJassValueVisitor.getInstance()); final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); final double facing = arguments.get(4).visit(RealJassValueVisitor.getInstance()); - final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(new War3ID(rawcode), - player.getId(), (float) x, (float) y, (float) facing); + final War3ID rawcodeId = new War3ID(rawcode); + final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(rawcodeId, player.getId(), + (float) x, (float) y, (float) facing); final CUnitType newUnitType = newUnit.getUnitType(); final int foodUsed = newUnitType.getFoodUsed(); newUnit.setFoodUsed(foodUsed); @@ -2498,6 +2469,7 @@ public class Jass2 { if (newUnitType.getFoodMade() != 0) { player.setFoodCap(player.getFoodCap() + newUnitType.getFoodMade()); } + player.addTechtreeUnlocked(rawcodeId); // nudge unit newUnit.setPointAndCheckUnstuck((float) x, (float) y, CommonEnvironment.this.simulation); return new HandleJassValue(unitType, newUnit); @@ -2513,8 +2485,9 @@ public class Jass2 { final double y = arguments.get(3).visit(RealJassValueVisitor.getInstance()); final double facing = arguments.get(4).visit(RealJassValueVisitor.getInstance()); final int skinId = arguments.get(5).visit(IntegerJassValueVisitor.getInstance()); - final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(new War3ID(rawcode), - player.getId(), (float) x, (float) y, (float) facing); + final War3ID rawcodeId = new War3ID(rawcode); + final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(rawcodeId, player.getId(), + (float) x, (float) y, (float) facing); final CUnitType newUnitType = newUnit.getUnitType(); final int foodUsed = newUnitType.getFoodUsed(); newUnit.setFoodUsed(foodUsed); @@ -2522,6 +2495,7 @@ public class Jass2 { if (newUnitType.getFoodMade() != 0) { player.setFoodCap(player.getFoodCap() + newUnitType.getFoodMade()); } + player.addTechtreeUnlocked(rawcodeId); // nudge unit newUnit.setPointAndCheckUnstuck((float) x, (float) y, CommonEnvironment.this.simulation); if (skinId != rawcode) { @@ -2540,8 +2514,9 @@ public class Jass2 { final int rawcode = arguments.get(1).visit(IntegerJassValueVisitor.getInstance()); final Point2D.Double whichLocation = arguments.get(2).visit(ObjectJassValueVisitor.getInstance()); final float facing = arguments.get(3).visit(RealJassValueVisitor.getInstance()).floatValue(); - final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(new War3ID(rawcode), - player.getId(), (float) whichLocation.x, (float) whichLocation.y, facing); + final War3ID rawcodeId = new War3ID(rawcode); + final CUnit newUnit = CommonEnvironment.this.simulation.createUnit(rawcodeId, player.getId(), + (float) whichLocation.x, (float) whichLocation.y, facing); final CUnitType newUnitType = newUnit.getUnitType(); final int foodUsed = newUnitType.getFoodUsed(); newUnit.setFoodUsed(foodUsed); @@ -2549,6 +2524,7 @@ public class Jass2 { if (newUnitType.getFoodMade() != 0) { player.setFoodCap(player.getFoodCap() + newUnitType.getFoodMade()); } + player.addTechtreeUnlocked(rawcodeId); // nudge unit newUnit.setPointAndCheckUnstuck((float) whichLocation.x, (float) whichLocation.y, CommonEnvironment.this.simulation); @@ -2560,12 +2536,14 @@ public class Jass2 { public JassValue call(final List arguments, final GlobalScope globalScope, final TriggerExecutionScope triggerScope) { // TODO this needs to setup a non-blighted mine underneath!!! - final CPlayerJass player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CPlayer player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); final double x = arguments.get(1).visit(RealJassValueVisitor.getInstance()); final double y = arguments.get(2).visit(RealJassValueVisitor.getInstance()); final double facing = arguments.get(3).visit(RealJassValueVisitor.getInstance()); - return new HandleJassValue(unitType, CommonEnvironment.this.simulation.createUnit( - War3ID.fromString("ugol"), player.getId(), (float) x, (float) y, (float) facing)); + final War3ID blightedMineRawcode = War3ID.fromString("ugol"); + player.addTechtreeUnlocked(blightedMineRawcode); + return new HandleJassValue(unitType, CommonEnvironment.this.simulation + .createUnit(blightedMineRawcode, player.getId(), (float) x, (float) y, (float) facing)); } }); jassProgramVisitor.getJassNativeManager().createNative("SetUnitColor", new JassFunction() { @@ -3796,7 +3774,10 @@ public class Jass2 { for (final CUnit unit : units) { if (unit.getPlayerIndex() == whichPlayer.getId()) { if (legacySystemUnitTypeName.equals(unit.getUnitType().getLegacyName())) { - if (includeIncomplete || !unit.isConstructing()) { + if ((includeIncomplete || !unit.isConstructing()) + && (includeUpgrades || !unit.isUpgrading())) { + // TODO this might not actually be what includeUpgrades means, it probably means + // to include higher tier of hall when asked for lower tier type ID count++; } } @@ -4724,6 +4705,18 @@ public class Jass2 { return null; } }); + jassProgramVisitor.getJassNativeManager().createNative("GetPlayerAlliance", new JassFunction() { + @Override + public JassValue call(final List arguments, final GlobalScope globalScope, + final TriggerExecutionScope triggerScope) { + final CPlayerJass player = arguments.get(0).visit(ObjectJassValueVisitor.getInstance()); + final CPlayerJass otherPlayer = arguments.get(1) + .visit(ObjectJassValueVisitor.getInstance()); + final CAllianceType whichAllianceSetting = arguments.get(2) + .visit(ObjectJassValueVisitor.getInstance()); + return BooleanJassValue.of(player.hasAlliance(otherPlayer.getId(), whichAllianceSetting)); + } + }); jassProgramVisitor.getJassNativeManager().createNative("SetPlayerTaxRate", new JassFunction() { @Override public JassValue call(final List arguments, final GlobalScope globalScope, diff --git a/core/src/com/etheller/warsmash/units/Element.java b/core/src/com/etheller/warsmash/units/Element.java index 15de6fd..c5ab889 100644 --- a/core/src/com/etheller/warsmash/units/Element.java +++ b/core/src/com/etheller/warsmash/units/Element.java @@ -183,6 +183,11 @@ public class Element extends HashedGameObject { return name; } + @Override + public String getLegacyName() { + return null; + } + public void addParent(final String parentId) { String parentField = getField("Parents"); if (!parentField.contains(parentId)) { diff --git a/core/src/com/etheller/warsmash/units/GameObject.java b/core/src/com/etheller/warsmash/units/GameObject.java index bc5f5d0..48d3134 100644 --- a/core/src/com/etheller/warsmash/units/GameObject.java +++ b/core/src/com/etheller/warsmash/units/GameObject.java @@ -30,6 +30,8 @@ public interface GameObject { public String getName(); + public String getLegacyName(); + public Set keySet(); GameObject EMPTY = new GameObject() { @@ -96,6 +98,11 @@ public interface GameObject { public String getField(final String field) { return ""; } + + @Override + public String getLegacyName() { + return "custom_0000"; + } }; } \ No newline at end of file diff --git a/core/src/com/etheller/warsmash/units/StandardObjectData.java b/core/src/com/etheller/warsmash/units/StandardObjectData.java index ed7c916..24f6c89 100644 --- a/core/src/com/etheller/warsmash/units/StandardObjectData.java +++ b/core/src/com/etheller/warsmash/units/StandardObjectData.java @@ -562,6 +562,21 @@ public class StandardObjectData { return this.dataSource; } + @Override + public String getLegacyName() { + final DataTable dataTable = this.dataSource.tableMap.get(new StringKey("UnitUI")); + if (dataTable != null) { + final Element element = dataTable.get(this.id); + if (element != null) { + return element.getField("name"); + } + else { + return null; + } + } + return null; + } + // @Override // public String getName() { // return dataSource.profile.get(id).getName(); diff --git a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java index 808d40e..e28662c 100644 --- a/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java +++ b/core/src/com/etheller/warsmash/units/manager/MutableObjectData.java @@ -745,6 +745,16 @@ public final class MutableObjectData { return name; } + public String getLegacyName() { + if (!isCustom()) { + final String legacyNameIfAvailable = this.parentWC3Object.getLegacyName(); + if (legacyNameIfAvailable != null) { + return legacyNameIfAvailable; + } + } + return "custom_" + getAlias().toString(); + } + private String getFieldStringFromSLKs(final War3ID field, final int level) { final GameObject metaData = MutableObjectData.this.sourceSLKMetaData.get(field.asStringValue()); if (metaData == null) { diff --git a/core/src/com/etheller/warsmash/util/Test3.java b/core/src/com/etheller/warsmash/util/Test3.java new file mode 100644 index 0000000..6f28bfe --- /dev/null +++ b/core/src/com/etheller/warsmash/util/Test3.java @@ -0,0 +1,7 @@ +package com.etheller.warsmash.util; + +public class Test3 { + public static void main(final String[] args) { + System.out.println(new War3ID(1786017909)); + } +} diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index b2b412c..13a60e6 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -25,9 +25,9 @@ public class WarsmashConstants { // find it yet so I used this public static final String DEFAULT_STRING = "Default string"; - public static final boolean CATCH_CURSOR = false; - public static final boolean VERBOSE_LOGGING = true; - public static final boolean ENABLE_DEBUG = true; + public static final boolean CATCH_CURSOR = true; + public static final boolean VERBOSE_LOGGING = false; + public static final boolean ENABLE_DEBUG = false; public static final char SPECIAL_ESCAPE_KEYCODE = 0x7E; // My tileset loader is "always on top", even for local files. This is different @@ -36,7 +36,7 @@ public class WarsmashConstants { // workaround to fix it if you need the local files // to take priority over built-ins for tilesets. public static final boolean FIX_FLAT_FILES_TILESET_LOADING = false; - public static final boolean ENABLE_MUSIC = false; + public static final boolean ENABLE_MUSIC = true; public static final boolean LOAD_UNITS_FROM_WORLDEDIT_DATA = false; public static final boolean CRASH_ON_INCOMPATIBLE_132_FEATURES = false; public static final boolean FIRE_DEATH_EVENTS_ON_REMOVEUNIT = false; diff --git a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java index 730d242..8994402 100644 --- a/core/src/com/etheller/warsmash/viewer5/ModelViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/ModelViewer.java @@ -316,19 +316,7 @@ public abstract class ModelViewer { public void render() { for (final Scene scene : this.scenes) { scene.startFrame(); - } - this.renderOpaque(); - this.renderTranslucent(); - } - - private void renderOpaque() { - for (final Scene scene : this.scenes) { scene.renderOpaque(); - } - } - - private void renderTranslucent() { - for (final Scene scene : this.scenes) { scene.renderTranslucent(); } } diff --git a/core/src/com/etheller/warsmash/viewer5/Scene.java b/core/src/com/etheller/warsmash/viewer5/Scene.java index edcf854..649371a 100644 --- a/core/src/com/etheller/warsmash/viewer5/Scene.java +++ b/core/src/com/etheller/warsmash/viewer5/Scene.java @@ -240,6 +240,9 @@ public abstract class Scene { gl.glDepthMask(true); gl.glClear(GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_COLOR_BUFFER_BIT); } + else { + Gdx.gl30.glClear(GL30.GL_DEPTH_BUFFER_BIT); + } this.lightManager.update(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java index 1cd642c..74a50d0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/SequenceUtils.java @@ -21,6 +21,7 @@ public class SequenceUtils { public static final EnumSet SPELL = EnumSet.of(SecondaryTag.SPELL); public static final EnumSet WORK = EnumSet.of(SecondaryTag.WORK); public static final EnumSet FAST = EnumSet.of(SecondaryTag.FAST); + public static final EnumSet ALTERNATE = EnumSet.of(SecondaryTag.ALTERNATE); private static final StandSequenceComparator STAND_SEQUENCE_COMPARATOR = new StandSequenceComparator(); private static final SecondaryTagSequenceComparator SECONDARY_TAG_SEQUENCE_COMPARATOR = new SecondaryTagSequenceComparator( diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java index 555e03e..4ffaebc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/War3MapViewer.java @@ -80,6 +80,7 @@ import com.etheller.warsmash.viewer5.handlers.mdx.MdxModel; import com.etheller.warsmash.viewer5.handlers.mdx.MdxNode; import com.etheller.warsmash.viewer5.handlers.mdx.SequenceLoopMode; import com.etheller.warsmash.viewer5.handlers.tga.TgaFile; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.SecondaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.SplatModel.SplatMover; import com.etheller.warsmash.viewer5.handlers.w3x.environment.BuildingShadow; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid.RemovablePathingMapInstance; @@ -643,6 +644,70 @@ public class War3MapViewer extends AbstractMdxModelViewer { } } + @Override + public void unitUpgradingEvent(final CUnit unit, final War3ID upgradeIdType) { +// final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.get(unit); + final MutableGameObject upgrade = War3MapViewer.this.allObjectData.getUnits() + .get(upgradeIdType); + + // TODO this should be behind some auto lookup so it isn't copied from + // RenderUnit class: + final String originalRequiredAnimationNames = War3MapViewer.this.allObjectData.getUnits() + .get(unit.getTypeId()).getFieldAsString(RenderUnit.ANIM_PROPS, 0); + TokenLoop: for (final String animationName : originalRequiredAnimationNames.split(",")) { + final String upperCaseToken = animationName.toUpperCase(); + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + unit.getUnitAnimationListener().removeSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } + // TODO this should be behind some auto lookup so it isn't copied from + // RenderUnit class: + final String requiredAnimationNames = upgrade.getFieldAsString(RenderUnit.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())) { + unit.getUnitAnimationListener().addSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } + } + + @Override + public void unitCancelUpgradingEvent(final CUnit unit, final War3ID upgradeIdType) { + final MutableGameObject upgrade = War3MapViewer.this.allObjectData.getUnits() + .get(upgradeIdType); + + // TODO this should be behind some auto lookup so it isn't copied from + // RenderUnit class: + final String requiredAnimationNames = upgrade.getFieldAsString(RenderUnit.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())) { + unit.getUnitAnimationListener().removeSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } + + final String originalRequiredAnimationNames = War3MapViewer.this.allObjectData.getUnits() + .get(unit.getTypeId()).getFieldAsString(RenderUnit.ANIM_PROPS, 0); + TokenLoop: for (final String animationName : originalRequiredAnimationNames.split(",")) { + final String upperCaseToken = animationName.toUpperCase(); + for (final SecondaryTag secondaryTag : SecondaryTag.values()) { + if (upperCaseToken.equals(secondaryTag.name())) { + unit.getUnitAnimationListener().addSecondaryTag(secondaryTag); + continue TokenLoop; + } + } + } + } + @Override public void removeUnit(final CUnit unit) { final RenderUnit renderUnit = War3MapViewer.this.unitToRenderPeer.remove(unit); @@ -1665,7 +1730,7 @@ public class War3MapViewer extends AbstractMdxModelViewer { final float rawDeltaTime = Gdx.graphics.getRawDeltaTime(); this.updateTime += rawDeltaTime; - while (this.updateTime >= WarsmashConstants.SIMULATION_STEP_TIME) { + while (this.updateTime >= (WarsmashConstants.SIMULATION_STEP_TIME)) { if (this.gameTurnManager.getLatestCompletedTurn() >= this.simulation.getGameTurnTick()) { this.updateTime -= WarsmashConstants.SIMULATION_STEP_TIME; this.simulation.update(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java index 00be6fe..4d02054 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderUnit.java @@ -35,7 +35,7 @@ public class RenderUnit implements RenderWidget { private static final War3ID BLUE = War3ID.fromString("uclb"); 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"); + public static final War3ID ANIM_PROPS = War3ID.fromString("uani"); private static final War3ID BLEND_TIME = War3ID.fromString("uble"); private static final War3ID BUILD_SOUND_LABEL = War3ID.fromString("ubsl"); private static final War3ID UNIT_SELECT_HEIGHT = War3ID.fromString("uslz"); @@ -409,7 +409,7 @@ public class RenderUnit implements RenderWidget { selectionCircleHeight + map.imageWalkableZOffset); } this.unitAnimationListenerImpl.update(); - if (!dead && this.simulationUnit.isConstructing()) { + if (!dead && this.simulationUnit.isConstructingOrUpgrading()) { this.instance.setFrameByRatio( this.simulationUnit.getConstructionProgress() / this.simulationUnit.getUnitType().getBuildTime()); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java index 9f5da3d..1982d97 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandButtonListener.java @@ -36,5 +36,6 @@ public interface CommandButtonListener { // int getOrderId(); void commandButton(int buttonPositionX, int buttonPositionY, Texture icon, int abilityHandleId, int orderId, int autoCastOrderId, boolean active, boolean autoCastActive, boolean menuButton, String tip, String uberTip, - char hotkey, int goldCost, int lumberCost, int foodCost); + char hotkey, int goldCost, int lumberCost, int foodCost, int manaCost, float cooldownRemaining, + float cooldownMax, int numberOverlay); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java index c54f0c5..9df3f63 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/commandbuttons/CommandCardPopulatingAbilityVisitor.java @@ -11,7 +11,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGenericDoNothing; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.AbstractCAbilityBuild; @@ -25,6 +25,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -107,10 +108,10 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor 0)) { + goldCost = 0; + lumberCost = 0; + } + else { + goldCost = simulationUnitType.getGoldCost(); + lumberCost = simulationUnitType.getLumberCost(); + } addCommandButton(ability, unitUI, ability.getHandleId(), unitType.getValue(), 0, false, false, - simulationUnitType.getGoldCost(), simulationUnitType.getLumberCost(), - simulationUnitType.getFoodUsed()); + goldCost, lumberCost, simulationUnitType.getFoodUsed(), 0, -1); } } if (ENABLE_PLACEHOLDERS) { @@ -341,10 +361,22 @@ public class CommandCardPopulatingAbilityVisitor implements CAbilityVisitor orderQueue = new LinkedList<>(); - private final CUnitType unitType; + private CUnitType unitType; private Rectangle collisionRectangle; private RemovablePathingMapInstance pathingInstance; @@ -119,6 +128,8 @@ public class CUnit extends CWidget { private transient CBehaviorStop stopBehavior; private transient CBehaviorHoldPosition holdPositionBehavior; private boolean constructing = false; + private boolean constructingPaused = false; + private War3ID upgradeIdType = null; private float constructionProgress; private boolean hidden = false; private boolean paused = false; @@ -152,6 +163,7 @@ public class CUnit extends CWidget { this.mana = mana; this.maximumLife = maximumLife; this.lifeRegen = lifeRegen; + this.manaRegen = unitType.getManaRegen(); this.maximumMana = maximumMana; this.speed = speed; this.pathingInstance = pathingInstance; @@ -171,6 +183,7 @@ public class CUnit extends CWidget { public void setLifeRegenStrengthBonus(final float lifeRegenStrengthBonus) { this.lifeRegenStrengthBonus = lifeRegenStrengthBonus; + computeDerivedFields(); } private void computeDerivedFields() { @@ -180,6 +193,13 @@ public class CUnit extends CWidget { this.currentDefense = this.currentDefenseDisplay + this.totalTemporaryDefenseBonus; this.currentLifeRegenPerTick = (this.lifeRegen + this.lifeRegenBonus + this.lifeRegenStrengthBonus) * WarsmashConstants.SIMULATION_STEP_TIME; + this.currentManaRegenPerTick = (this.manaRegen + this.manaRegenBonus + this.manaRegenIntelligenceBonus) + * WarsmashConstants.SIMULATION_STEP_TIME; + } + + public void setManaRegenIntelligenceBonus(final float manaRegenIntelligenceBonus) { + this.manaRegenIntelligenceBonus = manaRegenIntelligenceBonus; + computeDerivedFields(); } public void setLifeRegenBonus(final float lifeRegenBonus) { @@ -187,6 +207,11 @@ public class CUnit extends CWidget { computeDerivedFields(); } + public void setManaRegenBonus(final float manaRegenBonus) { + this.manaRegenBonus = manaRegenBonus; + computeDerivedFields(); + } + public void setAgilityDefensePermanentBonus(final int agilityDefensePermanentBonus) { this.agilityDefensePermanentBonus = agilityDefensePermanentBonus; computeDerivedFields(); @@ -263,8 +288,40 @@ public class CUnit extends CWidget { return this.maximumLife; } - public void setTypeId(final War3ID typeId) { + public void setTypeId(final CSimulation game, final War3ID typeId) { this.typeId = typeId; + final float lifeRatio = this.maximumLife == 0 ? 1 : (this.life / this.maximumLife); + final float manaRatio = this.maximumMana == 0 ? Float.NaN : (this.mana / this.maximumMana); + this.unitType = game.getUnitData().getUnitType(typeId); + if (Float.isNaN(manaRatio)) { + this.mana = this.unitType.getManaInitial(); + } + else { + this.maximumMana = this.unitType.getManaMaximum(); + this.mana = manaRatio * this.maximumMana; + } + this.maximumLife = this.unitType.getMaxLife(); + this.life = lifeRatio * this.maximumLife; + this.lifeRegen = this.unitType.getLifeRegen(); + this.manaRegen = this.unitType.getManaRegen(); + this.flyHeight = this.unitType.getDefaultFlyingHeight(); + this.classifications.clear(); + this.classifications.addAll(this.unitType.getClassifications()); + this.acquisitionRange = this.unitType.getDefaultAcquisitionRange(); + for (final CAbility ability : this.abilities) { + if (ability instanceof CAbilityQueue) { + ((CAbilityQueue) ability).onSetUnitType(this.unitType); + } + else if (ability instanceof CAbilityUpgrade) { + ((CAbilityUpgrade) ability).onSetUnitType(this.unitType); + } + else { + // refresh abilities... + ability.onRemove(game, this); + ability.onAdd(game, this); + } + } + computeDerivedFields(); } public void setFacing(final float facing) { @@ -363,13 +420,25 @@ public class CUnit extends CWidget { setRallyPoint(this); } if (this.constructing) { - this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; - final int buildTime = this.unitType.getBuildTime(); - final float healthGain = (WarsmashConstants.SIMULATION_STEP_TIME / buildTime) - * (this.maximumLife * (1.0f - WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE)); - setLife(game, Math.min(this.life + healthGain, this.maximumLife)); + if (!this.constructingPaused) { + this.constructionProgress += WarsmashConstants.SIMULATION_STEP_TIME; + } + final int buildTime; + final boolean upgrading = isUpgrading(); + if (!upgrading) { + buildTime = this.unitType.getBuildTime(); + if (!this.constructingPaused) { + final float healthGain = (WarsmashConstants.SIMULATION_STEP_TIME / buildTime) + * (this.maximumLife * (1.0f - WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE)); + setLife(game, Math.min(this.life + healthGain, this.maximumLife)); + } + } + else { + buildTime = game.getUnitData().getUnitType(this.upgradeIdType).getBuildTime(); + } if (this.constructionProgress >= buildTime) { this.constructing = false; + this.constructingPaused = false; this.constructionProgress = 0; popoutWorker(game); final Iterator abilityIterator = this.abilities.iterator(); @@ -384,12 +453,24 @@ public class CUnit extends CWidget { } } final CPlayer player = game.getPlayer(this.playerIndex); + if (upgrading) { + if (this.unitType.getFoodMade() != 0) { + player.setFoodCap(player.getFoodCap() - this.unitType.getFoodMade()); + } + setTypeId(game, this.upgradeIdType); + this.upgradeIdType = null; + } if (this.unitType.getFoodMade() != 0) { player.setFoodCap(player.getFoodCap() + this.unitType.getFoodMade()); } player.removeTechtreeInProgress(this.unitType.getTypeId()); player.addTechtreeUnlocked(this.unitType.getTypeId()); game.unitConstructFinishEvent(this); + if (upgrading) { + // TODO shouldnt need to play stand here, probably + getUnitAnimationListener().playAnimation(false, PrimaryTag.STAND, SequenceUtils.EMPTY, 1.0f, + true); + } this.stateNotifier.ordersChanged(); } } @@ -543,6 +624,14 @@ public class CUnit extends CWidget { this.stateNotifier.lifeChanged(); } } + if (this.mana < this.maximumMana) { + float manaPlusRegen = this.mana + this.currentManaRegenPerTick; + if (manaPlusRegen > this.maximumMana) { + manaPlusRegen = this.maximumMana; + } + this.mana = manaPlusRegen; + this.stateNotifier.manaChanged(); + } for (final CAbility ability : this.abilities) { ability.onTick(game, this); } @@ -964,7 +1053,14 @@ public class CUnit extends CWidget { damageRatioFromDefense = 2f - (float) StrictMath.pow(0.94, -defense); } final float trueDamage = damageRatioFromArmorClass * damageRatioFromDefense * damage; + final boolean wasAboveMax = this.life > this.maximumLife; this.life -= trueDamage; + if ((damage < 0) && !wasAboveMax && (this.life > this.maximumLife)) { + // NOTE wasAboveMax is for that weird life drain power to drain above max... to + // be honest that's a crazy mechanic anyway so I didn't test whether it works + // yet + this.life = this.maximumLife; + } this.stateNotifier.lifeChanged(); } simulation.unitDamageEvent(this, weaponType, this.unitType.getArmorType()); @@ -1221,6 +1317,10 @@ public class CUnit extends CWidget { @Override public boolean canBeTargetedBy(final CSimulation simulation, final CUnit source, final EnumSet targetsAllowed) { + if ((this == source) && targetsAllowed.contains(CTargetType.NOTSELF) + && !targetsAllowed.contains(CTargetType.SELF)) { + return false; + } if (targetsAllowed.containsAll(this.unitType.getTargetedAs()) || (!targetsAllowed.contains(CTargetType.GROUND) && (!targetsAllowed.contains(CTargetType.STRUCTURE) && !targetsAllowed.contains(CTargetType.AIR)))) { final int sourcePlayerIndex = source.getPlayerIndex(); @@ -1376,11 +1476,27 @@ public class CUnit extends CWidget { } } + public void setConstructingPaused(final boolean constructingPaused) { + this.constructingPaused = constructingPaused; + } + public void setConstructionProgress(final float constructionProgress) { this.constructionProgress = constructionProgress; } public boolean isConstructing() { + return this.constructing && (this.upgradeIdType == null); + } + + public boolean isUpgrading() { + return this.constructing && (this.upgradeIdType != null); + } + + public War3ID getUpgradeIdType() { + return this.upgradeIdType; + } + + public boolean isConstructingOrUpgrading() { return this.constructing; } @@ -1579,7 +1695,13 @@ public class CUnit extends CWidget { if (queue(game, rawcode, QueueItemType.UNIT)) { final CPlayer player = game.getPlayer(this.playerIndex); final CUnitType unitType = game.getUnitData().getUnitType(rawcode); - player.chargeFor(unitType); + final boolean isHeroType = unitType.isHero(); + if (isHeroType && (player.getHeroTokens() > 0)) { + player.setHeroTokens(player.getHeroTokens() - 1); + } + else { + player.chargeFor(unitType); + } } } @@ -1886,11 +2008,65 @@ public class CUnit extends CWidget { } public void cancelUpgrade(final CSimulation game) { - throw new RuntimeException("NYI"); + final CPlayer player = game.getPlayer(this.playerIndex); + player.setUnitFoodUsed(this, this.unitType.getFoodUsed()); + int goldCost, lumberCost; + final CUnitType newUpgradeUnitType = game.getUnitData().getUnitType(this.upgradeIdType); + if (game.getGameplayConstants().isRelativeUpgradeCosts()) { + goldCost = newUpgradeUnitType.getGoldCost() - this.unitType.getGoldCost(); + lumberCost = newUpgradeUnitType.getLumberCost() - this.unitType.getLumberCost(); + } + else { + goldCost = newUpgradeUnitType.getGoldCost(); + lumberCost = newUpgradeUnitType.getLumberCost(); + } + player.refund(goldCost, lumberCost); + + final Iterator abilityIterator = this.abilities.iterator(); + while (abilityIterator.hasNext()) { + final CAbility ability = abilityIterator.next(); + if (ability instanceof CAbilityBuildInProgress) { + abilityIterator.remove(); + } + else { + ability.setDisabled(false); + ability.setIconShowing(true); + } + } + + game.unitCancelUpgradingEvent(this, this.upgradeIdType); + this.upgradeIdType = null; + this.constructing = false; + this.constructionProgress = 0; + this.unitAnimationListener.playAnimation(true, PrimaryTag.STAND, SequenceUtils.EMPTY, 0.0f, true); } public void beginUpgrade(final CSimulation game, final War3ID rawcode) { + this.upgradeIdType = rawcode; + this.constructing = true; + this.constructionProgress = 0; + final CPlayer player = game.getPlayer(this.playerIndex); + final CUnitType newUpgradeUnitType = game.getUnitData().getUnitType(rawcode); + player.setUnitFoodUsed(this, newUpgradeUnitType.getFoodUsed()); + int goldCost, lumberCost; + if (game.getGameplayConstants().isRelativeUpgradeCosts()) { + goldCost = newUpgradeUnitType.getGoldCost() - this.unitType.getGoldCost(); + lumberCost = newUpgradeUnitType.getLumberCost() - this.unitType.getLumberCost(); + } + else { + goldCost = newUpgradeUnitType.getGoldCost(); + lumberCost = newUpgradeUnitType.getLumberCost(); + } + player.charge(goldCost, lumberCost); + add(game, new CAbilityBuildInProgress(game.getHandleIdAllocator().createId())); + for (final CAbility ability : getAbilities()) { + ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + } + player.addTechtreeInProgress(rawcode); + + game.unitUpgradingEvent(this, rawcode); + this.unitAnimationListener.playAnimation(true, PrimaryTag.BIRTH, SequenceUtils.EMPTY, 0.0f, true); } public void setUnitState(final CSimulation game, final CUnitState whichUnitState, final float value) { @@ -2036,4 +2212,18 @@ public class CUnit extends CWidget { } return unit.getUnitType().getName(); } + + public void fireCooldownsChangedEvent() { + this.stateNotifier.ordersChanged(); + } + + public int getAbilityLevel(final War3ID abilityId) { + final CLevelingAbility ability = getAbility(GetAbilityByRawcodeVisitor.getInstance().reset(abilityId)); + if (ability == null) { + return 0; + } + else { + return ability.getLevel(); + } + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java index 2f1356a..d93939f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitStateListener.java @@ -5,6 +5,8 @@ import com.etheller.warsmash.util.SubscriberSetNotifier; public interface CUnitStateListener { void lifeChanged(); // hp (current) changes + void manaChanged(); + void ordersChanged(); void queueChanged(); @@ -26,6 +28,13 @@ public interface CUnitStateListener { } } + @Override + public void manaChanged() { + for (final CUnitStateListener listener : set) { + listener.manaChanged(); + } + } + @Override public void ordersChanged() { for (final CUnitStateListener listener : set) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java index fcff676..c0e5ff5 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CUnitType.java @@ -1,6 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation; import java.awt.image.BufferedImage; +import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -25,6 +26,7 @@ public class CUnitType { private final War3ID typeId; private final int maxLife; private final float lifeRegen; + private final float manaRegen; private final CRegenType lifeRegenType; private final int manaInitial; private final int manaMaximum; @@ -81,10 +83,13 @@ public class CUnitType { private final int priority; private final boolean revivesHeroes; private final int pointValue; + private final List> requirementTiers; + private final float castBackswingPoint; + private final float castPoint; public CUnitType(final String name, final String legacyName, final War3ID typeId, final int maxLife, - final float lifeRegen, final CRegenType lifeRegenType, final int manaInitial, final int manaMaximum, - final int speed, final int defense, final String abilityList, final boolean isBldg, + final float lifeRegen, final float manaRegen, final CRegenType lifeRegenType, final int manaInitial, + final int manaMaximum, final int speed, final int defense, final String abilityList, final boolean isBldg, final MovementType movementType, final float defaultFlyingHeight, final float collisionSize, final EnumSet classifications, final List attacks, final String armorType, final boolean raise, final boolean decay, final CDefenseType defenseType, final float impactZ, @@ -94,16 +99,18 @@ public class CUnitType { final CUnitRace unitRace, final int goldCost, final int lumberCost, final int foodUsed, final int foodMade, final int buildTime, final EnumSet preventedPathingTypes, final EnumSet requiredPathingTypes, final float propWindow, final float turnRate, - final List requirements, final int level, final boolean hero, final int strength, - final float strengthPerLevel, final int agility, final float agilityPerLevel, final int intelligence, - final float intelligencePerLevel, final CPrimaryAttribute primaryAttribute, - final List heroAbilityList, final List heroProperNames, final int properNamesCount, - final boolean canFlee, final int priority, final boolean revivesHeroes, final int pointValue) { + final List requirements, final List> requirementTiers, + final int level, final boolean hero, final int strength, final float strengthPerLevel, final int agility, + final float agilityPerLevel, final int intelligence, final float intelligencePerLevel, + final CPrimaryAttribute primaryAttribute, final List heroAbilityList, + final List heroProperNames, final int properNamesCount, final boolean canFlee, final int priority, + final boolean revivesHeroes, final int pointValue, final float castBackswingPoint, final float castPoint) { this.name = name; this.legacyName = legacyName; this.typeId = typeId; this.maxLife = maxLife; this.lifeRegen = lifeRegen; + this.manaRegen = manaRegen; this.lifeRegenType = lifeRegenType; this.manaInitial = manaInitial; this.manaMaximum = manaMaximum; @@ -141,6 +148,7 @@ public class CUnitType { this.propWindow = propWindow; this.turnRate = turnRate; this.requirements = requirements; + this.requirementTiers = requirementTiers; this.level = level; this.hero = hero; this.startingStrength = strength; @@ -157,6 +165,8 @@ public class CUnitType { this.priority = priority; this.revivesHeroes = revivesHeroes; this.pointValue = pointValue; + this.castBackswingPoint = castBackswingPoint; + this.castPoint = castPoint; } public String getName() { @@ -179,6 +189,10 @@ public class CUnitType { return this.lifeRegen; } + public float getManaRegen() { + return this.manaRegen; + } + public CRegenType getLifeRegenType() { return this.lifeRegenType; } @@ -327,6 +341,16 @@ public class CUnitType { return this.requirements; } + public List getRequirementsTier(final int tier) { + final int index = tier - 1; + if ((index >= 0) && (index < this.requirementTiers.size())) { + return this.requirementTiers.get(index); + } + else { + return Collections.emptyList(); + } + } + public int getLevel() { return this.level; } @@ -390,4 +414,12 @@ public class CUnitType { public int getPointValue() { return this.pointValue; } + + public float getCastBackswingPoint() { + return this.castBackswingPoint; + } + + public float getCastPoint() { + return this.castPoint; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGenericDoNothing.java similarity index 88% rename from core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java rename to core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGenericDoNothing.java index df4e170..273708a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGeneric.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityGenericDoNothing.java @@ -4,6 +4,7 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericAliasedAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; @@ -13,16 +14,10 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC /** * Represents an ability from the object data */ -public class CAbilityGeneric extends AbstractCAbility { - private final War3ID rawcode; +public class CAbilityGenericDoNothing extends AbstractGenericAliasedAbility { - public CAbilityGeneric(final War3ID rawcode, final int handleId) { - super(handleId); - this.rawcode = rawcode; - } - - public War3ID getRawcode() { - return this.rawcode; + public CAbilityGenericDoNothing(final War3ID rawcode, final int handleId) { + super(handleId, rawcode); } @Override @@ -67,7 +62,8 @@ public class CAbilityGeneric extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { return false; } @@ -90,4 +86,5 @@ public class CAbilityGeneric extends AbstractCAbility { @Override public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java index a17d1e1..0c217b0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/CAbilityVisitor.java @@ -10,6 +10,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -37,7 +38,7 @@ public interface CAbilityVisitor { T accept(CAbilityNightElfBuild ability); - T accept(CAbilityGeneric ability); + T accept(CAbilityGenericDoNothing ability); T accept(CAbilityColdArrows ability); @@ -53,6 +54,8 @@ public interface CAbilityVisitor { T accept(CAbilityReviveHero ability); + T accept(CAbilityReturnResources ability); + T accept(GenericSingleIconActiveAbility ability); T accept(CAbilityRally ability); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/GetAbilityByRawcodeVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/GetAbilityByRawcodeVisitor.java index 554670f..950e7de 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/GetAbilityByRawcodeVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/GetAbilityByRawcodeVisitor.java @@ -9,15 +9,17 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityOrcBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityUndeadBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityReviveHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.upgrade.CAbilityUpgrade; -public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { +public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { private static final GetAbilityByRawcodeVisitor INSTANCE = new GetAbilityByRawcodeVisitor(); public static GetAbilityByRawcodeVisitor getInstance() { @@ -33,83 +35,37 @@ public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { } @Override - public CAbility accept(final CAbilityAttack ability) { + public CLevelingAbility accept(final CAbilityAttack ability) { return null; } @Override - public CAbility accept(final CAbilityMove ability) { + public CLevelingAbility accept(final CAbilityMove ability) { return null; } @Override - public CAbility accept(final CAbilityOrcBuild ability) { + public CLevelingAbility accept(final CAbilityOrcBuild ability) { return null; } @Override - public CAbility accept(final CAbilityHumanBuild ability) { + public CLevelingAbility accept(final CAbilityHumanBuild ability) { return null; } @Override - public CAbility accept(final CAbilityUndeadBuild ability) { + public CLevelingAbility accept(final CAbilityUndeadBuild ability) { return null; } @Override - public CAbility accept(final CAbilityNightElfBuild ability) { + public CLevelingAbility accept(final CAbilityNightElfBuild ability) { return null; } @Override - public CAbility accept(final CAbilityGeneric ability) { - if (this.rawcode.equals(ability.getRawcode())) { - return ability; - } - return null; - } - - @Override - public CAbility accept(final CAbilityColdArrows ability) { - if (this.rawcode.equals(ability.getRawcode())) { - return ability; - } - return null; - } - - @Override - public CAbility accept(final CAbilityNagaBuild ability) { - return null; - } - - @Override - public CAbility accept(final CAbilityNeutralBuild ability) { - return null; - } - - @Override - public CAbility accept(final CAbilityBuildInProgress ability) { - return null; - } - - @Override - public CAbility accept(final CAbilityQueue ability) { - return null; - } - - @Override - public CAbility accept(final CAbilityUpgrade ability) { - return null; - } - - @Override - public CAbility accept(final CAbilityReviveHero ability) { - return null; - } - - @Override - public CAbility accept(final GenericSingleIconActiveAbility ability) { + public CLevelingAbility accept(final CAbilityGenericDoNothing ability) { if (this.rawcode.equals(ability.getAlias())) { return ability; } @@ -117,7 +73,53 @@ public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { } @Override - public CAbility accept(final CAbilityRally ability) { + public CLevelingAbility accept(final CAbilityColdArrows ability) { + if (this.rawcode.equals(ability.getAlias())) { + return ability; + } + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityNagaBuild ability) { + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityNeutralBuild ability) { + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityBuildInProgress ability) { + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityQueue ability) { + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityUpgrade ability) { + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityReviveHero ability) { + return null; + } + + @Override + public CLevelingAbility accept(final GenericSingleIconActiveAbility ability) { + if (this.rawcode.equals(ability.getAlias())) { + return ability; + } + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityRally ability) { if (this.rawcode.equals(RALLY_RAWCODE)) { return ability; } @@ -125,7 +127,7 @@ public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { } @Override - public CAbility accept(final GenericNoIconAbility ability) { + public CLevelingAbility accept(final GenericNoIconAbility ability) { if (this.rawcode.equals(ability.getAlias())) { return ability; } @@ -133,7 +135,15 @@ public class GetAbilityByRawcodeVisitor implements CAbilityVisitor { } @Override - public CAbility accept(final CAbilityHero ability) { + public CLevelingAbility accept(final CAbilityReturnResources ability) { + if (this.rawcode.equals(ability.getAlias())) { + return ability; + } + return null; + } + + @Override + public CLevelingAbility accept(final CAbilityHero ability) { return null; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java index b576fdd..a75a5e4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/AbstractCAbilityBuild.java @@ -50,8 +50,8 @@ public abstract class AbstractCAbilityBuild extends AbstractCAbility implements } } if (requirementsMet) { - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { + if ((player.getGold() >= unitType.getGoldCost())) { + if ((player.getLumber() >= unitType.getLumberCost())) { if ((unitType.getFoodUsed() == 0) || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java index 727dd50..4b1c02b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityBuildInProgress.java @@ -38,10 +38,16 @@ public class CAbilityBuildInProgress extends AbstractCAbility { public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, final AbilityTarget target) { final CPlayer player = game.getPlayer(caster.getPlayerIndex()); - final CUnitType unitType = caster.getUnitType(); - player.refundFor(unitType); - player.removeTechtreeInProgress(unitType.getTypeId()); - caster.setLife(game, 0); + if (caster.isUpgrading()) { + player.removeTechtreeInProgress(caster.getUpgradeIdType()); + caster.cancelUpgrade(game); + } + else { + final CUnitType unitType = caster.getUnitType(); + player.refundFor(unitType); + player.removeTechtreeInProgress(unitType.getTypeId()); + caster.setLife(game, 0); + } return false; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java index b6f420f..676f426 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanBuild.java @@ -1,21 +1,26 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; +import java.awt.image.BufferedImage; import java.util.List; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build.CBehaviorHumanBuild; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; public class CAbilityHumanBuild extends AbstractCAbilityBuild { + private CBehaviorHumanBuild buildBehavior; + public CAbilityHumanBuild(final int handleId, final List structuresBuilt) { super(handleId, structuresBuilt); - // TODO Auto-generated constructor stub } @Override @@ -25,33 +30,45 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { @Override public void onAdd(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - + this.buildBehavior = new CBehaviorHumanBuild(unit); } @Override public void onRemove(final CSimulation game, final CUnit unit) { - // TODO Auto-generated method stub - } @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { - // TODO Auto-generated method stub - return null; + return caster.pollNextOrderBehavior(game); } @Override public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final AbilityPointTarget point) { -// caster.getMoveBehavior().reset(point.x, point.y, ) - return null; + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); + final BufferedImage buildingPathingPixelMap = unitType.getBuildingPathingPixelMap(); + if (buildingPathingPixelMap != null) { + point.x = (float) Math.floor(point.x / 64f) * 64f; + point.y = (float) Math.floor(point.y / 64f) * 64f; + if (((buildingPathingPixelMap.getWidth() / 2) % 2) == 1) { + point.x += 32f; + } + if (((buildingPathingPixelMap.getHeight() / 2) % 2) == 1) { + point.y += 32f; + } + } + final CPlayer player = game.getPlayer(caster.getPlayerIndex()); + player.chargeFor(unitType); + if (unitType.getFoodUsed() != 0) { + player.setFoodUsed(player.getFoodUsed() + unitType.getFoodUsed()); + } + return this.buildBehavior.reset(point, orderId, getBaseOrderId()); } @Override public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { - // TODO Auto-generated method stub - return null; + return caster.pollNextOrderBehavior(game); } @Override @@ -59,10 +76,4 @@ public class CAbilityHumanBuild extends AbstractCAbilityBuild { return visitor.accept(this); } - @Override - public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - // TODO Auto-generated method stub - - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanRepair.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanRepair.java index 3b1ec14..4531290 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanRepair.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/build/CAbilityHumanRepair.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build; +import java.util.EnumSet; + import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; @@ -13,123 +15,150 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -import java.util.EnumSet; - public class CAbilityHumanRepair extends AbstractGenericSingleIconActiveAbility { - private EnumSet targetsAllowed; - private final float navalRangeBonus; - private final float repairCostRatio; - private final float repairTimeRatio; - private final float castRange; - private CBehaviorHumanRepair behaviorRepair; + private EnumSet targetsAllowed; + private float navalRangeBonus; + private float repairCostRatio; + private float repairTimeRatio; + private float castRange; + private CBehaviorHumanRepair behaviorRepair; - public CAbilityHumanRepair(int handleId, War3ID alias, EnumSet targetsAllowed, - float navalRangeBonus, float repairCostRatio, float repairTimeRatio, - float castRange) { - super(handleId, alias); - this.targetsAllowed = targetsAllowed; - this.navalRangeBonus = navalRangeBonus; - this.repairCostRatio = repairCostRatio; - this.repairTimeRatio = repairTimeRatio; - this.castRange = castRange; - } + public CAbilityHumanRepair(final int handleId, final War3ID alias, final EnumSet targetsAllowed, + final float navalRangeBonus, final float repairCostRatio, final float repairTimeRatio, + final float castRange) { + super(handleId, alias); + this.targetsAllowed = targetsAllowed; + this.navalRangeBonus = navalRangeBonus; + this.repairCostRatio = repairCostRatio; + this.repairTimeRatio = repairTimeRatio; + this.castRange = castRange; + } - @Override - protected void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, AbilityTargetCheckReceiver receiver) { - if(target.canBeTargetedBy(game, unit, targetsAllowed) && target.getLife() < target.getMaxLife()) { - receiver.targetOk(target); - } else { - receiver.orderIdNotAccepted(); - } - } + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + if (target.canBeTargetedBy(game, unit, this.targetsAllowed) && (target.getLife() < target.getMaxLife())) { + receiver.targetOk(target); + } + else { + receiver.orderIdNotAccepted(); + } + } - @Override - protected void innerCheckCanSmartTarget(CSimulation game, CUnit unit, int orderId, CWidget target, AbilityTargetCheckReceiver receiver) { - innerCheckCanTarget(game, unit, orderId, target, receiver); - } + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + innerCheckCanTarget(game, unit, orderId, target, receiver); + } - @Override - protected void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, AbilityTargetCheckReceiver receiver) { - receiver.mustTargetType(AbilityTargetCheckReceiver.TargetType.UNIT); - } + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.mustTargetType(AbilityTargetCheckReceiver.TargetType.UNIT); + } - @Override - protected void innerCheckCanSmartTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, AbilityTargetCheckReceiver receiver) { - innerCheckCanTarget(game, unit, orderId, target, receiver); - } + @Override + protected void innerCheckCanSmartTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + innerCheckCanTarget(game, unit, orderId, target, receiver); + } - @Override - protected void innerCheckCanTargetNoTarget(CSimulation game, CUnit unit, int orderId, AbilityTargetCheckReceiver receiver) { - receiver.orderIdNotAccepted(); - } + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } - @Override - protected void innerCheckCanUse(CSimulation game, CUnit unit, int orderId, AbilityActivationReceiver receiver) { - receiver.useOk(); - } + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } - @Override - public void onAdd(CSimulation game, CUnit unit) { - behaviorRepair = new CBehaviorHumanRepair(unit, this); - } + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorRepair = new CBehaviorHumanRepair(unit, this); + } - @Override - public void onRemove(CSimulation game, CUnit unit) { + @Override + public void onRemove(final CSimulation game, final CUnit unit) { - } + } - @Override - public void onTick(CSimulation game, CUnit unit) { + @Override + public void onTick(final CSimulation game, final CUnit unit) { - } + } - @Override - public void onCancelFromQueue(CSimulation game, CUnit unit, int orderId) { + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - } + } - @Override - public CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target) { - return behaviorRepair.reset(target); - } + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return this.behaviorRepair.reset(target); + } - @Override - public CBehavior begin(CSimulation game, CUnit caster, int orderId, AbilityPointTarget point) { - return null; - } + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } - @Override - public CBehavior beginNoTarget(CSimulation game, CUnit caster, int orderId) { - return null; - } + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } - @Override - public int getBaseOrderId() { - return OrderIds.repair; - } + @Override + public int getBaseOrderId() { + return OrderIds.repair; + } - @Override - public boolean isToggleOn() { - return false; - } + @Override + public boolean isToggleOn() { + return false; + } - public EnumSet getTargetsAllowed() { - return targetsAllowed; - } + public EnumSet getTargetsAllowed() { + return this.targetsAllowed; + } - public float getNavalRangeBonus() { - return navalRangeBonus; - } + public float getNavalRangeBonus() { + return this.navalRangeBonus; + } - public float getRepairCostRatio() { - return repairCostRatio; - } + public float getRepairCostRatio() { + return this.repairCostRatio; + } - public float getRepairTimeRatio() { - return repairTimeRatio; - } + public float getRepairTimeRatio() { + return this.repairTimeRatio; + } + + public float getCastRange() { + return this.castRange; + } + + public void setTargetsAllowed(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } + + public void setNavalRangeBonus(final float navalRangeBonus) { + this.navalRangeBonus = navalRangeBonus; + } + + public void setRepairCostRatio(final float repairCostRatio) { + this.repairCostRatio = repairCostRatio; + } + + public void setRepairTimeRatio(final float repairTimeRatio) { + this.repairTimeRatio = repairTimeRatio; + } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } - public float getCastRange() { - return castRange; - } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java index 14dd9a0..88df0d3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/combat/CAbilityColdArrows.java @@ -4,8 +4,8 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericAliasedAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; @@ -18,13 +18,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC /** * Represents an ability from the object data */ -public class CAbilityColdArrows extends AbstractCAbility { - private final War3ID rawcode; +public class CAbilityColdArrows extends AbstractGenericAliasedAbility { private boolean autoCastActive; public CAbilityColdArrows(final War3ID rawcode, final int handleId) { - super(handleId); - this.rawcode = rawcode; + super(handleId, rawcode); } @Override @@ -66,10 +64,6 @@ public class CAbilityColdArrows extends AbstractCAbility { } } - public War3ID getRawcode() { - return this.rawcode; - } - public boolean isAutoCastActive() { return this.autoCastActive; } @@ -92,7 +86,8 @@ public class CAbilityColdArrows extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { switch (orderId) { case OrderIds.coldarrows: case OrderIds.uncoldarrows: diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericAliasedAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericAliasedAbility.java new file mode 100644 index 0000000..1490724 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericAliasedAbility.java @@ -0,0 +1,37 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; + +public abstract class AbstractGenericAliasedAbility extends AbstractCAbility implements CLevelingAbility { + private final War3ID alias; + private int level = 1; + + public AbstractGenericAliasedAbility(final int handleId, final War3ID alias) { + super(handleId); + this.alias = alias; + } + + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { + return true; + } + + public War3ID getAlias() { + return this.alias; + } + + @Override + public final int getLevel() { + return this.level; + } + + @Override + public void setLevel(final int level) { + this.level = level; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java index 8abdcae..5112b25 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericNoIconAbility.java @@ -1,24 +1,13 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; -public abstract class AbstractGenericNoIconAbility extends AbstractCAbility implements GenericNoIconAbility { - private final War3ID alias; +public abstract class AbstractGenericNoIconAbility extends AbstractGenericAliasedAbility + implements GenericNoIconAbility { public AbstractGenericNoIconAbility(final int handleId, final War3ID alias) { - super(handleId); - this.alias = alias; - } - - @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, - final AbilityTarget target) { - return true; + super(handleId, alias); } @Override @@ -26,9 +15,4 @@ public abstract class AbstractGenericNoIconAbility extends AbstractCAbility impl return visitor.accept(this); } - @Override - public War3ID getAlias() { - return this.alias; - } - } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java index a9a536e..ef823b9 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/AbstractGenericSingleIconActiveAbility.java @@ -4,20 +4,17 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAbility +public abstract class AbstractGenericSingleIconActiveAbility extends AbstractGenericAliasedAbility implements GenericSingleIconActiveAbility { - private final War3ID alias; public AbstractGenericSingleIconActiveAbility(final int handleId, final War3ID alias) { - super(handleId); - this.alias = alias; + super(handleId, alias); } @Override @@ -85,11 +82,6 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb return visitor.accept(this); } - @Override - public War3ID getAlias() { - return this.alias; - } - @Override public int getUIGoldCost() { return 0; @@ -100,4 +92,9 @@ public abstract class AbstractGenericSingleIconActiveAbility extends AbstractCAb return 0; } + @Override + public int getUIManaCost() { + return 0; + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/CLevelingAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/CLevelingAbility.java new file mode 100644 index 0000000..9c24c8b --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/CLevelingAbility.java @@ -0,0 +1,9 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; + +public interface CLevelingAbility extends CAbility { + int getLevel(); + + void setLevel(int level); +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java index fb6dd4c..05eac2e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericNoIconAbility.java @@ -1,8 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -public interface GenericNoIconAbility extends CAbility { +public interface GenericNoIconAbility extends CLevelingAbility { War3ID getAlias(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java index c52a396..fcbab22 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/generic/GenericSingleIconActiveAbility.java @@ -1,9 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic; import com.etheller.warsmash.util.War3ID; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -public interface GenericSingleIconActiveAbility extends CAbility { +public interface GenericSingleIconActiveAbility extends CLevelingAbility { War3ID getAlias(); int getBaseOrderId(); @@ -13,4 +12,6 @@ public interface GenericSingleIconActiveAbility extends CAbility { int getUIGoldCost(); int getUILumberCost(); + + int getUIManaCost(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java index 8e34f18..54b1f06 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityHarvest.java @@ -27,11 +27,11 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { - private final int damageToTree; - private final int goldCapacity; - private final int lumberCapacity; - private final float castRange; - private final float duration; + private int damageToTree; + private int goldCapacity; + private int lumberCapacity; + private float castRange; + private float duration; private CBehaviorHarvest behaviorHarvest; private CBehaviorReturnResources behaviorReturnResources; private int carriedResourceAmount; @@ -233,4 +233,23 @@ public class CAbilityHarvest extends AbstractGenericSingleIconActiveAbility { return this.behaviorTreeAttack; } + public void setDamageToTree(final int damageToTree) { + this.damageToTree = damageToTree; + } + + public void setGoldCapacity(final int goldCapacity) { + this.goldCapacity = goldCapacity; + } + + public void setLumberCapacity(final int lumberCapacity) { + this.lumberCapacity = lumberCapacity; + } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } + + public void setDuration(final float duration) { + this.duration = duration; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java index c3d1051..b18ff19 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityReturnResources.java @@ -6,7 +6,8 @@ import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericNoIconAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericAliasedAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; @@ -16,8 +17,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; /** * Was probably named CAbilityReturn in 2002, idk */ -public class CAbilityReturnResources extends AbstractGenericNoIconAbility { - private final EnumSet acceptedResourceTypes; +public class CAbilityReturnResources extends AbstractGenericAliasedAbility { + private EnumSet acceptedResourceTypes; public CAbilityReturnResources(final int handleId, final War3ID alias, final EnumSet acceptedResourceTypes) { @@ -25,6 +26,11 @@ public class CAbilityReturnResources extends AbstractGenericNoIconAbility { this.acceptedResourceTypes = acceptedResourceTypes; } + @Override + public T visit(final CAbilityVisitor visitor) { + return visitor.accept(this); + } + @Override public void onAdd(final CSimulation game, final CUnit unit) { @@ -89,4 +95,8 @@ public class CAbilityReturnResources extends AbstractGenericNoIconAbility { } return this.acceptedResourceTypes.contains(resourceType); } + + public void setAcceptedResourceTypes(final EnumSet acceptedResourceTypes) { + this.acceptedResourceTypes = acceptedResourceTypes; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityWispHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityWispHarvest.java index 65a98f8..41706e2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityWispHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/harvest/CAbilityWispHarvest.java @@ -1,5 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest; +import java.util.EnumSet; + import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CDestructable; @@ -15,8 +17,6 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -import java.util.EnumSet; - public class CAbilityWispHarvest extends AbstractGenericSingleIconActiveAbility { public static final EnumSet TREE_ALIVE_TYPE_ONLY = EnumSet.of(CTargetType.TREE, CTargetType.ALIVE); @@ -27,15 +27,14 @@ public class CAbilityWispHarvest extends AbstractGenericSingleIconActiveAbility private int periodicIntervalLengthTicks; private CBehaviorWispHarvest behaviorWispHarvest; - - public CAbilityWispHarvest(final int handleId, final War3ID alias, final int lumberPerInterval, float artAttachmentHeight, - float castRange, final float periodicIntervalLength) { + public CAbilityWispHarvest(final int handleId, final War3ID alias, final int lumberPerInterval, + final float artAttachmentHeight, final float castRange, final float periodicIntervalLength) { super(handleId, alias); this.lumberPerInterval = lumberPerInterval; this.artAttachmentHeight = artAttachmentHeight; this.castRange = castRange; this.periodicIntervalLength = periodicIntervalLength; - this.periodicIntervalLengthTicks = (int)(periodicIntervalLength / WarsmashConstants.SIMULATION_STEP_TIME); + this.periodicIntervalLengthTicks = (int) (periodicIntervalLength / WarsmashConstants.SIMULATION_STEP_TIME); } @Override @@ -129,27 +128,44 @@ public class CAbilityWispHarvest extends AbstractGenericSingleIconActiveAbility } public float getArtAttachmentHeight() { - return artAttachmentHeight; + return this.artAttachmentHeight; } public float getPeriodicIntervalLength() { - return periodicIntervalLength; + return this.periodicIntervalLength; } public int getPeriodicIntervalLengthTicks() { - return periodicIntervalLengthTicks; + return this.periodicIntervalLengthTicks; } public int getLumberPerInterval() { - return lumberPerInterval; + return this.lumberPerInterval; } public float getCastRange() { - return castRange; + return this.castRange; } @Override public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + public void setLumberPerInterval(final int lumberPerInterval) { + this.lumberPerInterval = lumberPerInterval; + } + + public void setArtAttachmentHeight(final float artAttachmentHeight) { + this.artAttachmentHeight = artAttachmentHeight; + } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } + + public void setPeriodicIntervalLength(final float periodicIntervalLength) { + this.periodicIntervalLength = periodicIntervalLength; + this.periodicIntervalLengthTicks = (int) (periodicIntervalLength / WarsmashConstants.SIMULATION_STEP_TIME); + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java index f32bdb8..2a26aa0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/hero/CAbilityHero.java @@ -1,6 +1,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import com.etheller.warsmash.util.War3ID; import com.etheller.warsmash.util.WarsmashConstants; @@ -10,19 +12,24 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.GetAbilityByRawcodeVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.attacks.CUnitAttack; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.data.CAbilityData; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; public class CAbilityHero extends AbstractCAbility { - private final List skillsAvailable; + private final Set skillsAvailable; private int xp; private int heroLevel; - private int skillPoints; + private int skillPoints = 1; private HeroStatValue strength; private HeroStatValue agility; @@ -33,7 +40,7 @@ public class CAbilityHero extends AbstractCAbility { public CAbilityHero(final int handleId, final List skillsAvailable) { super(handleId); - this.skillsAvailable = skillsAvailable; + this.skillsAvailable = new LinkedHashSet<>(skillsAvailable); } @Override @@ -83,7 +90,25 @@ public class CAbilityHero extends AbstractCAbility { @Override public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, final AbilityTarget target) { - return true; + final War3ID orderIdAsRawtype = new War3ID(orderId); + final CAbilityType abilityType = game.getAbilityData().getAbilityType(orderIdAsRawtype); + if (abilityType != null) { + this.skillPoints--; + final CLevelingAbility existingAbility = caster + .getAbility(GetAbilityByRawcodeVisitor.getInstance().reset(orderIdAsRawtype)); + if (existingAbility == null) { + final CAbility newAbility = abilityType.createAbility(game.getHandleIdAllocator().createId()); + caster.add(game, newAbility); + } + else { + abilityType.setLevel(game, existingAbility, existingAbility.getLevel() + 1); + } + } + else { + game.getCommandErrorListener().showCommandError(caster.getPlayerIndex(), + "NOTEXTERN: Ability is not yet programmed, unable to learn!"); + } + return false; } @Override @@ -117,7 +142,13 @@ public class CAbilityHero extends AbstractCAbility { @Override public void checkCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, final AbilityTargetCheckReceiver receiver) { - receiver.orderIdNotAccepted(); + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.skillsAvailable.contains(orderIdAsRawtype)) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } } @Override @@ -128,7 +159,28 @@ public class CAbilityHero extends AbstractCAbility { @Override protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { - receiver.useOk(); + final War3ID orderIdAsRawtype = new War3ID(orderId); + if (this.skillsAvailable.contains(orderIdAsRawtype)) { + if (this.skillPoints > 0) { + final CAbilityData abilityData = game.getAbilityData(); + final int priorLevel = unit.getAbilityLevel(orderIdAsRawtype); + final int heroRequiredLevel = abilityData.getHeroRequiredLevel(game, orderIdAsRawtype, priorLevel); + final CAbilityType abilityType = abilityData.getAbilityType(orderIdAsRawtype); + // TODO check abilityType.getRequiredLevel() which api doesn't currently offer!! + if (this.heroLevel >= heroRequiredLevel) { + receiver.useOk(); + } + else { + receiver.missingHeroLevelRequirement(heroRequiredLevel); + } + } + else { + receiver.noHeroSkillPointsAvailable(); + } + } + else { + receiver.useOk(); + } } public int getSkillPoints() { @@ -252,7 +304,8 @@ public class CAbilityHero extends AbstractCAbility { this.intelligence.calculate(this.heroLevel); final int currentStrength = this.strength.getCurrent(); final int deltaStrength = currentStrength - prevStrength; - final int deltaIntelligence = this.intelligence.getCurrent() - prevIntelligence; + final int currentIntelligence = this.intelligence.getCurrent(); + final int deltaIntelligence = currentIntelligence - prevIntelligence; final int currentAgilityBase = this.agility.getBase(); final int currentAgilityBonus = this.agility.getBonus(); @@ -287,6 +340,7 @@ public class CAbilityHero extends AbstractCAbility { unit.setAgilityDefensePermanentBonus(agilityDefenseBonus); unit.setAgilityDefenseTemporaryBonus(gameplayConstants.getAgiDefenseBonus() * currentAgilityBonus); unit.setLifeRegenStrengthBonus(currentStrength * gameplayConstants.getStrRegenBonus()); + unit.setManaRegenIntelligenceBonus(currentIntelligence * gameplayConstants.getIntRegenBonus()); } public static final class HeroStatValue { @@ -339,4 +393,8 @@ public class CAbilityHero extends AbstractCAbility { return text; } } + + public Set getSkillsAvailable() { + return this.skillsAvailable; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/item/CAbilityItemHeal.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/item/CAbilityItemHeal.java index e5ff52a..fc0110a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/item/CAbilityItemHeal.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/item/CAbilityItemHeal.java @@ -13,89 +13,96 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivat import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; public class CAbilityItemHeal extends AbstractGenericSingleIconNoSmartActiveAbility { - private final int lifeToRegain; + private final int lifeToRegain; - public CAbilityItemHeal(int handleId, War3ID alias, int lifeToRegain) { - super(handleId, alias); - this.lifeToRegain = lifeToRegain; - } + public CAbilityItemHeal(final int handleId, final War3ID alias, final int lifeToRegain) { + super(handleId, alias); + this.lifeToRegain = lifeToRegain; + } - @Override - protected void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, CWidget target, AbilityTargetCheckReceiver receiver) { - receiver.orderIdNotAccepted(); - } + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } - @Override - protected void innerCheckCanTarget(CSimulation game, CUnit unit, int orderId, AbilityPointTarget target, AbilityTargetCheckReceiver receiver) { - receiver.orderIdNotAccepted(); - } + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } - @Override - protected void innerCheckCanTargetNoTarget(CSimulation game, CUnit unit, int orderId, AbilityTargetCheckReceiver receiver) { - if(orderId == getBaseOrderId()) { - receiver.targetOk(null); - } else { - receiver.orderIdNotAccepted(); - } - } + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + if (orderId == getBaseOrderId()) { + receiver.targetOk(null); + } + else { + receiver.orderIdNotAccepted(); + } + } - @Override - protected void innerCheckCanUse(CSimulation game, CUnit unit, int orderId, AbilityActivationReceiver receiver) { - receiver.useOk(); - } + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + receiver.useOk(); + } - @Override - public int getBaseOrderId() { - return OrderIds.heal; - } + @Override + public int getBaseOrderId() { + return OrderIds.heal; + } - @Override - public boolean isToggleOn() { - return false; - } + @Override + public boolean isToggleOn() { + return false; + } - @Override - public void onAdd(CSimulation game, CUnit unit) { + @Override + public void onAdd(final CSimulation game, final CUnit unit) { - } + } - @Override - public void onRemove(CSimulation game, CUnit unit) { + @Override + public void onRemove(final CSimulation game, final CUnit unit) { - } + } - @Override - public void onTick(CSimulation game, CUnit unit) { + @Override + public void onTick(final CSimulation game, final CUnit unit) { - } + } - @Override - public void onCancelFromQueue(CSimulation game, CUnit unit, int orderId) { + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { - } + } - @Override - public boolean checkBeforeQueue(CSimulation game, CUnit caster, int orderId, AbilityTarget target) { - if(target == null && orderId == getBaseOrderId()) { - caster.heal(game, lifeToRegain); - game.createSpellEffectOnUnit(caster, getAlias()); - return false; - } - return super.checkBeforeQueue(game, caster, orderId, target); - } + @Override + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { + if ((target == null) && (orderId == getBaseOrderId())) { + caster.heal(game, this.lifeToRegain); + game.createSpellEffectOnUnit(caster, getAlias()); + return false; + } + return super.checkBeforeQueue(game, caster, orderId, target); + } - @Override - public CBehavior begin(CSimulation game, CUnit caster, int orderId, CWidget target) { - return null; - } + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return null; + } - @Override - public CBehavior begin(CSimulation game, CUnit caster, int orderId, AbilityPointTarget point) { - return null; - } + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } - @Override - public CBehavior beginNoTarget(CSimulation game, CUnit caster, int orderId) { - return null; - } + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java index 3a28cc6..bd54687 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/mine/CAbilityGoldMine.java @@ -17,8 +17,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC public class CAbilityGoldMine extends AbstractGenericNoIconAbility { private int gold; - private final float miningDuration; - private final int miningCapacity; + private float miningDuration; + private int miningCapacity; private final List activeMiners; private boolean wasEmpty; @@ -137,4 +137,11 @@ public class CAbilityGoldMine extends AbstractGenericNoIconAbility { return this.miningDuration; } + public void setMiningCapacity(final int miningCapacity) { + this.miningCapacity = miningCapacity; + } + + public void setMiningDuration(final float miningDuration) { + this.miningDuration = miningDuration; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java index f750f85..bba44c6 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityQueue.java @@ -55,9 +55,20 @@ public final class CAbilityQueue extends AbstractCAbility { requirementsMet = false; } } + final boolean isHeroType = unitType.isHero(); + if (isHeroType) { + final int heroCount = player.getHeroCount(game, true); + final List requirementsTier = unitType.getRequirementsTier(heroCount); + for (final CUnitTypeRequirement requirement : requirementsTier) { + if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement.getRequiredLevel()) { + requirementsMet = false; + } + } + } + final boolean skipGoldLumberCost = isHeroType && (player.getHeroTokens() > 0); if (requirementsMet) { - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { + if ((player.getGold() >= unitType.getGoldCost()) || skipGoldLumberCost) { + if ((player.getLumber() >= unitType.getLumberCost()) || skipGoldLumberCost) { if ((unitType.getFoodUsed() == 0) || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { receiver.useOk(); @@ -79,6 +90,17 @@ public final class CAbilityQueue extends AbstractCAbility { for (final CUnitTypeRequirement requirement : requirements) { receiver.missingRequirement(requirement.getRequirement(), requirement.getRequiredLevel()); } + if (isHeroType) { + final int heroCount = player.getHeroCount(game, true); + final List requirementsTier = unitType.getRequirementsTier(heroCount); + for (final CUnitTypeRequirement requirement : requirementsTier) { + if (player.getTechtreeUnlocked(requirement.getRequirement()) < requirement + .getRequiredLevel()) { + receiver.missingRequirement(requirement.getRequirement(), + requirement.getRequiredLevel()); + } + } + } } else { receiver.techtreeMaximumReached(); @@ -177,4 +199,11 @@ public final class CAbilityQueue extends AbstractCAbility { @Override public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + + public void onSetUnitType(final CUnitType unitType) { + this.unitsTrained.clear(); + this.researchesAvailable.clear(); + this.unitsTrained.addAll(unitType.getUnitsTrained()); + this.researchesAvailable.addAll(unitType.getResearchesAvailable()); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java index 4f2fecc..2fdf536 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/queue/CAbilityRally.java @@ -5,6 +5,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.AbstractCAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; @@ -12,7 +13,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityActivationReceiver; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetCheckReceiver; -public class CAbilityRally extends AbstractCAbility { +public class CAbilityRally extends AbstractCAbility implements CLevelingAbility { public CAbilityRally(final int handleId) { super(handleId); @@ -73,7 +74,8 @@ public class CAbilityRally extends AbstractCAbility { } @Override - public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, AbilityTarget target) { + public boolean checkBeforeQueue(final CSimulation game, final CUnit caster, final int orderId, + final AbilityTarget target) { return true; } @@ -108,4 +110,13 @@ public class CAbilityRally extends AbstractCAbility { public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + @Override + public int getLevel() { + return 1; // TODO maybe less hacky solution + } + + @Override + public void setLevel(final int level) { + } + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/skills/human/paladin/CAbilityHolyLight.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/skills/human/paladin/CAbilityHolyLight.java new file mode 100644 index 0000000..665ba68 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/skills/human/paladin/CAbilityHolyLight.java @@ -0,0 +1,200 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.skills.human.paladin; + +import java.util.EnumSet; + +import com.etheller.warsmash.util.War3ID; +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.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.AbstractGenericSingleIconNoSmartActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.skills.human.paladin.CBehaviorHolyLight; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CAllianceType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +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; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; + +public class CAbilityHolyLight extends AbstractGenericSingleIconNoSmartActiveAbility { + private int manaCost; + private int healAmount; + private float castRange; + private float cooldown; + private EnumSet targetsAllowed; + private float cooldownRemaining; + private CBehaviorHolyLight behaviorHolyLight; + + public CAbilityHolyLight(final int handleId, final War3ID alias, final int manaCost, final int healAmount, + final float castRange, final float cooldown, final EnumSet targetsAllowed) { + super(handleId, alias); + this.manaCost = manaCost; + this.healAmount = healAmount; + this.castRange = castRange; + this.cooldown = cooldown; + this.targetsAllowed = targetsAllowed; + } + + @Override + public int getBaseOrderId() { + return OrderIds.holybolt; + } + + @Override + public boolean isToggleOn() { + return false; + } + + @Override + public void onAdd(final CSimulation game, final CUnit unit) { + this.behaviorHolyLight = new CBehaviorHolyLight(unit, this); + } + + @Override + public void onRemove(final CSimulation game, final CUnit unit) { + + } + + @Override + public void onTick(final CSimulation game, final CUnit unit) { + if (this.cooldownRemaining > 0) { + this.cooldownRemaining -= WarsmashConstants.SIMULATION_STEP_TIME; + } + } + + @Override + public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { + + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, final CWidget target) { + return this.behaviorHolyLight.reset(target); + } + + @Override + public CBehavior begin(final CSimulation game, final CUnit caster, final int orderId, + final AbilityPointTarget point) { + return null; + } + + @Override + public CBehavior beginNoTarget(final CSimulation game, final CUnit caster, final int orderId) { + return null; + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final CWidget target, final AbilityTargetCheckReceiver receiver) { + if ((target instanceof CUnit) && target.canBeTargetedBy(game, unit, this.targetsAllowed)) { + if (!unit.isMovementDisabled() || unit.canReach(target, this.castRange)) { + final CUnit targetUnit = (CUnit) target; + final CPlayer player = game.getPlayer(unit.getPlayerIndex()); + final boolean undead = targetUnit.getClassifications().contains(CUnitClassification.UNDEAD); + final boolean ally = player.hasAlliance(targetUnit.getPlayerIndex(), CAllianceType.PASSIVE); + if (undead != ally) { + if (ally && (targetUnit.getLife() >= targetUnit.getMaximumLife())) { + receiver.alreadyFullHealth(); + } + else { + receiver.targetOk(targetUnit); + } + } + else { + receiver.notHolyBoltTarget(); + } + } + else { + receiver.targetOutsideRange(); + } + } + else { + receiver.mustTargetType(TargetType.UNIT); + } + } + + @Override + protected void innerCheckCanTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityPointTarget target, final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanTargetNoTarget(final CSimulation game, final CUnit unit, final int orderId, + final AbilityTargetCheckReceiver receiver) { + receiver.orderIdNotAccepted(); + } + + @Override + protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, + final AbilityActivationReceiver receiver) { + if (this.cooldownRemaining > 0) { + receiver.cooldownNotYetReady(this.cooldownRemaining, this.cooldown); + } + else if (unit.getMana() < this.manaCost) { + receiver.notEnoughResources(ResourceType.MANA); + } + else { + receiver.useOk(); + } + } + + public void setManaCost(final int manaCost) { + this.manaCost = manaCost; + } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } + + public void setCooldown(final float cooldown) { + this.cooldown = cooldown; + } + + public void setHealAmount(final int healAmount) { + this.healAmount = healAmount; + } + + public void setTargetsAllowed(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } + + public void setCooldownRemaining(final float cooldownRemaining) { + this.cooldownRemaining = cooldownRemaining; + } + + public int getManaCost() { + return this.manaCost; + } + + @Override + public int getUIManaCost() { + return getManaCost(); + } + + public int getHealAmount() { + return this.healAmount; + } + + public float getCastRange() { + return this.castRange; + } + + public float getCooldown() { + return this.cooldown; + } + + public EnumSet getTargetsAllowed() { + return this.targetsAllowed; + } + + public float getCooldownRemaining() { + return this.cooldownRemaining; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCarrionSwarmDummy.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCarrionSwarmDummy.java index 662a94f..8a97f62 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCarrionSwarmDummy.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCarrionSwarmDummy.java @@ -17,8 +17,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC public class CAbilityCarrionSwarmDummy extends AbstractGenericSingleIconNoSmartActiveAbility { - private final float castRange; - private final EnumSet targetsAllowed; + private float castRange; + private EnumSet targetsAllowed; private CBehaviorCarrionSwarmDummy behaviorCarrionSwarmDummy; public CAbilityCarrionSwarmDummy(final int handleId, final War3ID alias, final float castRange, @@ -115,4 +115,12 @@ public class CAbilityCarrionSwarmDummy extends AbstractGenericSingleIconNoSmartA public EnumSet getTargetsAllowed() { return this.targetsAllowed; } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } + + public void setTargetsAllowed(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java index db50488..15e9612 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityChannelTest.java @@ -14,7 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.AbilityTargetC public class CAbilityChannelTest extends AbstractGenericSingleIconNoSmartActiveAbility { private CBehaviorChannelTest behaviorChannelTest; - private final float artDuration; + private float artDuration; public CAbilityChannelTest(final int handleId, final War3ID alias, final float artDuration) { super(handleId, alias); @@ -88,4 +88,7 @@ public class CAbilityChannelTest extends AbstractGenericSingleIconNoSmartActiveA public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + public void setArtDuration(final float artDuration) { + this.artDuration = artDuration; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java index 00325dc..9320b0b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/test/CAbilityCoupleInstant.java @@ -23,15 +23,15 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.ResourceType; public class CAbilityCoupleInstant extends AbstractGenericSingleIconNoSmartActiveAbility { - private final War3ID resultingUnitType; - private final War3ID partnerUnitType; - private final boolean moveToPartner; - private final float castRange; - private final float area; - private final EnumSet targetsAllowed; + private War3ID resultingUnitType; + private War3ID partnerUnitType; + private boolean moveToPartner; + private float castRange; + private float area; + private EnumSet targetsAllowed; private CBehaviorCoupleInstant behaviorCoupleInstant; - private final int goldCost; - private final int lumberCost; + private int goldCost; + private int lumberCost; public CAbilityCoupleInstant(final int handleId, final War3ID alias, final War3ID resultingUnitType, final War3ID partnerUnitType, final boolean moveToPartner, final float castRange, final float area, @@ -210,4 +210,36 @@ public class CAbilityCoupleInstant extends AbstractGenericSingleIconNoSmartActiv public int getUILumberCost() { return this.lumberCost; } + + public void setResultingUnitType(final War3ID resultingUnitType) { + this.resultingUnitType = resultingUnitType; + } + + public void setPartnerUnitType(final War3ID partnerUnitType) { + this.partnerUnitType = partnerUnitType; + } + + public void setMoveToPartner(final boolean moveToPartner) { + this.moveToPartner = moveToPartner; + } + + public void setCastRange(final float castRange) { + this.castRange = castRange; + } + + public void setArea(final float area) { + this.area = area; + } + + public void setTargetsAllowed(final EnumSet targetsAllowed) { + this.targetsAllowed = targetsAllowed; + } + + public void setGoldCost(final int goldCost) { + this.goldCost = goldCost; + } + + public void setLumberCost(final int lumberCost) { + this.lumberCost = lumberCost; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java index 5c8725b..8fcb04a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/CAbilityType.java @@ -4,7 +4,9 @@ import java.util.EnumSet; import java.util.List; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; public abstract class CAbilityType { @@ -39,4 +41,6 @@ public abstract class CAbilityType createAbilityType(final War3ID alias, final MutableGameObject abilityEditorData) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHolyLight.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHolyLight.java new file mode 100644 index 0000000..8a2bfaa --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/definitions/impl/CAbilityTypeDefinitionHolyLight.java @@ -0,0 +1,36 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl; + +import java.util.EnumSet; +import java.util.List; + +import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; +import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeHolyLight; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl.CAbilityTypeHolyLightLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeDefinitionHolyLight extends AbstractCAbilityTypeDefinition + implements CAbilityTypeDefinition { + private static final War3ID HEAL_AMOUNT = War3ID.fromString("Hhb1"); + + @Override + protected CAbilityTypeHolyLightLevelData createLevelData(final MutableGameObject abilityEditorData, + final int level) { + final String targetsAllowedAtLevelString = abilityEditorData.getFieldAsString(TARGETS_ALLOWED, level); + final float castRange = abilityEditorData.getFieldAsFloat(CAST_RANGE, level); + final int healAmount = (int) abilityEditorData.getFieldAsFloat(HEAL_AMOUNT, level); + final float cooldown = abilityEditorData.getFieldAsFloat(COOLDOWN, level); + final int manaCost = abilityEditorData.getFieldAsInteger(MANA_COST, level); + final EnumSet targetsAllowedAtLevel = CTargetType.parseTargetTypeSet(targetsAllowedAtLevelString); + return new CAbilityTypeHolyLightLevelData(targetsAllowedAtLevel, castRange, cooldown, healAmount, manaCost); + } + + @Override + protected CAbilityType innerCreateAbilityType(final War3ID alias, final MutableGameObject abilityEditorData, + final List levelData) { + return new CAbilityTypeHolyLight(alias, abilityEditorData.getCode(), levelData); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCarrionSwarmDummy.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCarrionSwarmDummy.java index 958f37d..432f40e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCarrionSwarmDummy.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeCarrionSwarmDummy.java @@ -3,7 +3,9 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.im import java.util.List; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.test.CAbilityCarrionSwarmDummy; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; @@ -21,4 +23,13 @@ public class CAbilityTypeCarrionSwarmDummy extends CAbilityType { @@ -19,4 +21,11 @@ public class CAbilityTypeColdArrows extends CAbilityType { + + public CAbilityTypeHolyLight(final War3ID alias, final War3ID code, + final List levelData) { + super(alias, code, levelData); + } + + @Override + public CAbility createAbility(final int handleId) { + final CAbilityTypeHolyLightLevelData levelData = getLevelData(0); + return new CAbilityHolyLight(handleId, getAlias(), levelData.getManaCost(), levelData.getHealAmount(), + levelData.getCastRange(), levelData.getCooldown(), levelData.getTargetsAllowed()); + } + + @Override + public void setLevel(final CSimulation game, final CLevelingAbility existingAbility, final int level) { + + final CAbilityTypeHolyLightLevelData levelData = getLevelData(level - 1); + final CAbilityHolyLight heroAbility = ((CAbilityHolyLight) existingAbility); + + heroAbility.setManaCost(levelData.getManaCost()); + heroAbility.setHealAmount(levelData.getHealAmount()); + heroAbility.setCastRange(levelData.getCastRange()); + heroAbility.setCooldown(levelData.getCooldown()); + heroAbility.setTargetsAllowed(levelData.getTargetsAllowed()); + + heroAbility.setLevel(level); + + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHolyLightLevelData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHolyLightLevelData.java new file mode 100644 index 0000000..e62bc9e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHolyLightLevelData.java @@ -0,0 +1,39 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; + +import java.util.EnumSet; + +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityTypeLevelData; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CTargetType; + +public class CAbilityTypeHolyLightLevelData extends CAbilityTypeLevelData { + private final float castRange; + private final float cooldown; + private final int healAmount; + private final int manaCost; + + public CAbilityTypeHolyLightLevelData(final EnumSet targetsAllowed, final float castRange, + final float cooldown, final int healAmount, final int manaCost) { + super(targetsAllowed); + this.castRange = castRange; + this.cooldown = cooldown; + this.healAmount = healAmount; + this.manaCost = manaCost; + } + + public float getCastRange() { + return this.castRange; + } + + public float getCooldown() { + return this.cooldown; + } + + public int getHealAmount() { + return this.healAmount; + } + + public int getManaCost() { + return this.manaCost; + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHumanRepair.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHumanRepair.java index e1c7692..80d4a81 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHumanRepair.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeHumanRepair.java @@ -1,16 +1,18 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; +import java.util.List; + import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanRepair; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; -import java.util.List; - public class CAbilityTypeHumanRepair extends CAbilityType { public CAbilityTypeHumanRepair(final War3ID alias, final War3ID code, - final List levelData) { + final List levelData) { super(alias, code, levelData); } @@ -22,4 +24,20 @@ public class CAbilityTypeHumanRepair extends CAbilityType { @@ -20,4 +23,15 @@ public class CAbilityTypeItemAttackBonus extends CAbilityType { @@ -20,4 +23,15 @@ public class CAbilityTypeItemDefenseBonus extends CAbilityType { public CAbilityTypeItemHeal(final War3ID alias, final War3ID code, - final List levelData) { + final List levelData) { super(alias, code, levelData); } @@ -21,4 +23,15 @@ public class CAbilityTypeItemHeal extends CAbilityType { @@ -20,4 +23,15 @@ public class CAbilityTypeItemLifeBonus extends CAbilityType { @@ -21,4 +24,15 @@ public class CAbilityTypeItemPermanentStatGain extends CAbilityType { @@ -21,4 +24,15 @@ public class CAbilityTypeItemStatBonus extends CAbilityType acceptedResourceTypes = EnumSet.noneOf(ResourceType.class); + if (levelData.isAcceptsGold()) { + acceptedResourceTypes.add(ResourceType.GOLD); + } + if (levelData.isAcceptsLumber()) { + acceptedResourceTypes.add(ResourceType.LUMBER); + } + heroAbility.setAcceptedResourceTypes(acceptedResourceTypes); + + heroAbility.setLevel(level); + + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeWispHarvest.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeWispHarvest.java index 7f1f524..e7bbc59 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeWispHarvest.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/types/impl/CAbilityTypeWispHarvest.java @@ -1,25 +1,38 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.impl; +import java.util.List; + import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityHarvest; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.CLevelingAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityWispHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; -import java.util.List; - public class CAbilityTypeWispHarvest extends CAbilityType { public CAbilityTypeWispHarvest(final War3ID alias, final War3ID code, - final List levelData) { + final List levelData) { super(alias, code, levelData); } @Override public CAbility createAbility(final int handleId) { final CAbilityTypeWispHarvestLevelData levelData = getLevelData(0); - return new CAbilityWispHarvest(handleId, getAlias(), levelData.getLumberPerInterval(), levelData.getArtAttachmentHeight(), - levelData.getCastRange(), levelData.getDuration()); + return new CAbilityWispHarvest(handleId, getAlias(), levelData.getLumberPerInterval(), + levelData.getArtAttachmentHeight(), levelData.getCastRange(), levelData.getDuration()); } + @Override + public void setLevel(final CSimulation game, final CLevelingAbility existingAbility, final int level) { + final CAbilityTypeWispHarvestLevelData levelData = getLevelData(level - 1); + final CAbilityWispHarvest heroAbility = ((CAbilityWispHarvest) existingAbility); + + heroAbility.setLumberPerInterval(levelData.getLumberPerInterval()); + heroAbility.setArtAttachmentHeight(levelData.getArtAttachmentHeight()); + heroAbility.setCastRange(levelData.getCastRange()); + heroAbility.setPeriodicIntervalLength(levelData.getDuration()); + + heroAbility.setLevel(level); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java index 77e0912..bcede2b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/abilities/upgrade/CAbilityUpgrade.java @@ -36,6 +36,10 @@ public final class CAbilityUpgrade extends AbstractCAbility { @Override protected void innerCheckCanUse(final CSimulation game, final CUnit unit, final int orderId, final AbilityActivationReceiver receiver) { + if (unit.getBuildQueueTypes()[0] != null) { + receiver.disabled(); + return; + } final War3ID orderIdAsRawtype = new War3ID(orderId); if (this.upgradesTo.contains(orderIdAsRawtype) && (unit.getBuildQueue()[0] == null)) { final CUnitType unitType = game.getUnitData().getUnitType(orderIdAsRawtype); @@ -50,10 +54,21 @@ public final class CAbilityUpgrade extends AbstractCAbility { } } if (requirementsMet) { - if (player.getGold() >= unitType.getGoldCost()) { - if (player.getLumber() >= unitType.getLumberCost()) { - if ((unitType.getFoodUsed() == 0) - || ((player.getFoodUsed() + unitType.getFoodUsed()) <= player.getFoodCap())) { + int relativeOffsetGold; + int relativeOffsetLumber; + final CUnitType existingUnitType = unit.getUnitType(); + if (game.getGameplayConstants().isRelativeUpgradeCosts()) { + relativeOffsetGold = existingUnitType.getGoldCost(); + relativeOffsetLumber = existingUnitType.getLumberCost(); + } + else { + relativeOffsetGold = 0; + relativeOffsetLumber = 0; + } + if ((player.getGold() + relativeOffsetGold) >= unitType.getGoldCost()) { + if ((player.getLumber() + relativeOffsetLumber) >= unitType.getLumberCost()) { + final int foodNeeded = unitType.getFoodUsed() - existingUnitType.getFoodUsed(); + if ((foodNeeded == 0) || ((player.getFoodUsed() + foodNeeded) <= player.getFoodCap())) { receiver.useOk(); } else { @@ -168,4 +183,9 @@ public final class CAbilityUpgrade extends AbstractCAbility { @Override public void onCancelFromQueue(final CSimulation game, final CUnit unit, final int orderId) { } + + public void onSetUnitType(final CUnitType unitType) { + this.upgradesTo.clear(); + this.upgradesTo.addAll(unitType.getUpgradesTo()); + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java index 95e0aed..9ecd56e 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/AbilityDisableWhileUnderConstructionVisitor.java @@ -1,7 +1,7 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityAttack; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGenericDoNothing; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityBuildInProgress; @@ -14,6 +14,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityQueue; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.queue.CAbilityRally; @@ -66,7 +67,7 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi } @Override - public Void accept(final CAbilityGeneric ability) { + public Void accept(final CAbilityGenericDoNothing ability) { ability.setDisabled(true); ability.setIconShowing(false); return null; @@ -142,6 +143,11 @@ public class AbilityDisableWhileUnderConstructionVisitor implements CAbilityVisi return null; } + @Override + public Void accept(final CAbilityReturnResources ability) { + return null; + } + @Override public Void accept(final CAbilityHero ability) { ability.setDisabled(true); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanBuild.java new file mode 100644 index 0000000..55025e9 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanBuild.java @@ -0,0 +1,160 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; + +import java.awt.image.BufferedImage; +import java.util.EnumSet; + +import com.etheller.warsmash.util.War3ID; +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.CUnitType; +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.build.CAbilityBuildInProgress; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanRepair; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.pathing.CBuildingPathingType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayer; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityActivationReceiver; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.BooleanAbilityTargetCheckReceiver; + +public class CBehaviorHumanBuild extends CAbstractRangedBehavior { + private int highlightOrderId; + private War3ID orderId; + private boolean unitCreated = false; + + public CBehaviorHumanBuild(final CUnit unit) { + super(unit); + } + + public CBehavior reset(final AbilityPointTarget target, final int orderId, final int highlightOrderId) { + this.highlightOrderId = highlightOrderId; + this.orderId = new War3ID(orderId); + this.unitCreated = false; + return innerReset(target); + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + final CUnitType unitType = simulation.getUnitData().getUnitType(this.orderId); + return this.unit.canReachToPathing(0, simulation.getGameplayConstants().getBuildingAngle(), + unitType.getBuildingPathingPixelMap(), this.target.getX(), this.target.getY()); + } + + @Override + public int getHighlightOrderId() { + return this.highlightOrderId; + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinFacingWindow) { + if (!this.unitCreated) { + this.unitCreated = true; + final CUnitType unitTypeToCreate = simulation.getUnitData().getUnitType(this.orderId); + final BufferedImage buildingPathingPixelMap = unitTypeToCreate.getBuildingPathingPixelMap(); + boolean buildLocationObstructed = false; + if (buildingPathingPixelMap != null) { + final EnumSet preventedPathingTypes = unitTypeToCreate.getPreventedPathingTypes(); + final EnumSet requiredPathingTypes = unitTypeToCreate.getRequiredPathingTypes(); + + if (!simulation.getPathingGrid().checkPathingTexture(this.target.getX(), this.target.getY(), + (int) simulation.getGameplayConstants().getBuildingAngle(), buildingPathingPixelMap, + preventedPathingTypes, requiredPathingTypes, simulation.getWorldCollision(), this.unit)) { + buildLocationObstructed = true; + } + } + final int playerIndex = this.unit.getPlayerIndex(); + if (!buildLocationObstructed) { + final CUnit constructedStructure = simulation.createUnit(this.orderId, playerIndex, this.target.getX(), + this.target.getY(), simulation.getGameplayConstants().getBuildingAngle()); + constructedStructure.setConstructing(true); + constructedStructure.setConstructingPaused(true); + constructedStructure.setLife(simulation, + constructedStructure.getMaximumLife() * WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE); + constructedStructure.setFoodUsed(unitTypeToCreate.getFoodUsed()); + constructedStructure.add(simulation, + new CAbilityBuildInProgress(simulation.getHandleIdAllocator().createId())); + for (final CAbility ability : constructedStructure.getAbilities()) { + ability.visit(AbilityDisableWhileUnderConstructionVisitor.INSTANCE); + } + final float deltaX = this.unit.getX() - this.target.getX(); + final float deltaY = this.unit.getY() - this.target.getY(); + final float delta = (float) Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); + this.unit.setPointAndCheckUnstuck( + this.target.getX() + ((deltaX / delta) * unitTypeToCreate.getCollisionSize()), + this.target.getY() + ((deltaY / delta) * unitTypeToCreate.getCollisionSize()), simulation); + simulation.unitRepositioned(this.unit); + simulation.getPlayer(playerIndex).addTechtreeInProgress(this.orderId); + simulation.unitConstructedEvent(this.unit, constructedStructure); + for (final CAbility ability : this.unit.getAbilities()) { + if (ability instanceof CAbilityHumanRepair) { + final int baseOrderId = ((CAbilityHumanRepair) ability).getBaseOrderId(); + ability.checkCanUse(simulation, this.unit, baseOrderId, + BooleanAbilityActivationReceiver.INSTANCE); + if (BooleanAbilityActivationReceiver.INSTANCE.isOk()) { + final BooleanAbilityTargetCheckReceiver targetCheckReceiver = BooleanAbilityTargetCheckReceiver + .getInstance(); + ability.checkCanTarget(simulation, this.unit, baseOrderId, constructedStructure, + targetCheckReceiver.reset()); + if (targetCheckReceiver.isTargetable()) { + return ability.begin(simulation, this.unit, baseOrderId, constructedStructure); + } + } + } + } + } + else { + final CPlayer player = simulation.getPlayer(playerIndex); + refund(player, unitTypeToCreate); + simulation.getCommandErrorListener().showCantPlaceError(playerIndex); + } + } + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return true; + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + + } + + @Override + public void begin(final CSimulation game) { + + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + + private void refund(final CPlayer player, final CUnitType unitTypeToCreate) { + player.setFoodUsed(player.getFoodUsed() - unitTypeToCreate.getFoodUsed()); + player.refundFor(unitTypeToCreate); + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + if (!this.unitCreated && interrupted) { + final CPlayer player = game.getPlayer(this.unit.getPlayerIndex()); + final CUnitType unitTypeToCreate = game.getUnitData().getUnitType(this.orderId); + refund(player, unitTypeToCreate); + } + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanRepair.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanRepair.java index 3305554..62ed1f4 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanRepair.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorHumanRepair.java @@ -1,88 +1,107 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.build; +import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens; import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; import com.etheller.warsmash.viewer5.handlers.w3x.environment.PathingGrid; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.*; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAbilityHumanRepair; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityPointTarget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveAndTargetableVisitor; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetVisitor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.harvest.CBehaviorHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; public class CBehaviorHumanRepair extends CAbstractRangedBehavior { - private final CAbilityHumanRepair ability; - private AbilityTargetStillAliveAndTargetableVisitor stillAliveVisitor; + private final CAbilityHumanRepair ability; + private final AbilityTargetStillAliveAndTargetableVisitor stillAliveVisitor; - public CBehaviorHumanRepair(CUnit unit, CAbilityHumanRepair ability) { - super(unit); - this.ability = ability; - stillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); - } + public CBehaviorHumanRepair(final CUnit unit, final CAbilityHumanRepair ability) { + super(unit); + this.ability = ability; + this.stillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); + } - public CBehaviorHumanRepair reset(final CWidget target) { - innerReset(target, false); - return this; - } + public CBehaviorHumanRepair reset(final CWidget target) { + innerReset(target, false); + return this; + } - @Override - public boolean isWithinRange(CSimulation simulation) { - float castRange = ability.getCastRange(); - if(target instanceof CUnit) { - CUnit unitTarget = (CUnit)target; - if(unitTarget.getUnitType().getMovementType() == PathingGrid.MovementType.FLOAT) { - castRange += ability.getNavalRangeBonus(); - } - } - return unit.canReach(target, castRange); - } + @Override + public boolean isWithinRange(final CSimulation simulation) { + float castRange = this.ability.getCastRange(); + if (this.target instanceof CUnit) { + final CUnit unitTarget = (CUnit) this.target; + if (unitTarget.getUnitType().getMovementType() == PathingGrid.MovementType.FLOAT) { + castRange += this.ability.getNavalRangeBonus(); + } + } + return this.unit.canReach(this.target, castRange); + } - @Override - protected CBehavior update(CSimulation simulation, boolean withinFacingWindow) { - unit.getUnitAnimationListener().playAnimation(false, AnimationTokens.PrimaryTag.STAND, - SequenceUtils.WORK, 1.0f, true); - if(this.target instanceof CWidget) { - CWidget targetWidget = (CWidget) this.target; - float newLifeValue = targetWidget.getLife() + 1; - boolean done = newLifeValue > targetWidget.getMaxLife(); - if(done) { - newLifeValue = targetWidget.getMaxLife(); - } - targetWidget.setLife(simulation, newLifeValue); - if(done) { - return unit.pollNextOrderBehavior(simulation); - } - } - return this; - } + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinFacingWindow) { + this.unit.getUnitAnimationListener().playAnimation(false, AnimationTokens.PrimaryTag.STAND, SequenceUtils.WORK, + 1.0f, true); + if (this.target instanceof CWidget) { + final CWidget targetWidget = (CWidget) this.target; + if ((targetWidget instanceof CUnit) && ((CUnit) targetWidget).isConstructing()) { + final CUnit targetUnit = (CUnit) targetWidget; + targetUnit.setConstructionProgress( + targetUnit.getConstructionProgress() + WarsmashConstants.SIMULATION_STEP_TIME); + final int buildTime = targetUnit.getUnitType().getBuildTime(); + final float healthGain = (WarsmashConstants.SIMULATION_STEP_TIME / buildTime) + * (targetUnit.getMaximumLife() * (1.0f - WarsmashConstants.BUILDING_CONSTRUCT_START_LIFE)); + targetUnit.setLife(simulation, + Math.min(targetUnit.getLife() + healthGain, targetUnit.getMaximumLife())); + if (targetUnit.getConstructionProgress() >= buildTime) { + return this.unit.pollNextOrderBehavior(simulation); + } + } + else { + float newLifeValue = targetWidget.getLife() + 1; + final boolean done = newLifeValue > targetWidget.getMaxLife(); + if (done) { + newLifeValue = targetWidget.getMaxLife(); + } + targetWidget.setLife(simulation, newLifeValue); + if (done) { + return this.unit.pollNextOrderBehavior(simulation); + } + } + } + return this; + } - @Override - protected CBehavior updateOnInvalidTarget(CSimulation simulation) { - return unit.pollNextOrderBehavior(simulation); - } + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } - @Override - protected boolean checkTargetStillValid(CSimulation simulation) { - return target.visit(stillAliveVisitor.reset(simulation, unit, ability.getTargetsAllowed())); - } + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(this.stillAliveVisitor.reset(simulation, this.unit, this.ability.getTargetsAllowed())); + } - @Override - protected void resetBeforeMoving(CSimulation simulation) {} + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + } - @Override - public void begin(CSimulation game) {} + @Override + public void begin(final CSimulation game) { + } - @Override - public void end(CSimulation game, boolean interrupted) {} + @Override + public void end(final CSimulation game, final boolean interrupted) { + } - @Override - public void endMove(CSimulation game, boolean interrupted) {} + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + } - @Override - public int getHighlightOrderId() { - return OrderIds.repair; - } + @Override + public int getHighlightOrderId() { + return OrderIds.repair; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java index 9ffe1c9..d3f2579 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorOrcBuild.java @@ -78,6 +78,7 @@ public class CBehaviorOrcBuild extends CAbstractRangedBehavior { this.unit.setHidden(true); this.unit.setPaused(true); this.unit.setInvulnerable(true); + simulation.getPlayer(playerIndex).addTechtreeInProgress(this.orderId); simulation.unitConstructedEvent(this.unit, constructedStructure); } else { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java index 4087b9b..b47f07a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/build/CBehaviorUndeadBuild.java @@ -94,10 +94,11 @@ public class CBehaviorUndeadBuild extends CAbstractRangedBehavior { final float deltaX = this.unit.getX() - this.target.getX(); final float deltaY = this.unit.getY() - this.target.getY(); final float delta = (float) Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)); - this.unit.setPoint(this.target.getX() + ((deltaX / delta) * unitTypeToCreate.getCollisionSize()), - this.target.getY() + ((deltaY / delta) * unitTypeToCreate.getCollisionSize()), - simulation.getWorldCollision(), simulation.getRegionManager()); + this.unit.setPointAndCheckUnstuck( + this.target.getX() + ((deltaX / delta) * unitTypeToCreate.getCollisionSize()), + this.target.getY() + ((deltaY / delta) * unitTypeToCreate.getCollisionSize()), simulation); simulation.unitRepositioned(this.unit); + simulation.getPlayer(playerIndex).addTechtreeInProgress(this.orderId); simulation.unitConstructedEvent(this.unit, constructedStructure); this.doneTick = simulation.getGameTurnTick() + delayAnimationTicks; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/skills/human/paladin/CBehaviorHolyLight.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/skills/human/paladin/CBehaviorHolyLight.java new file mode 100644 index 0000000..4461ce2 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/behaviors/skills/human/paladin/CBehaviorHolyLight.java @@ -0,0 +1,117 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.skills.human.paladin; + +import com.etheller.warsmash.util.WarsmashConstants; +import com.etheller.warsmash.viewer5.handlers.w3x.SequenceUtils; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnit; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CUnitClassification; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.skills.human.paladin.CAbilityHolyLight; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.targeting.AbilityTargetStillAliveAndTargetableVisitor; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CAbstractRangedBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.behaviors.CBehavior; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.combat.CAttackType; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.orders.OrderIds; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CWeaponSoundTypeJass; + +public class CBehaviorHolyLight extends CAbstractRangedBehavior { + private final CAbilityHolyLight ability; + private final AbilityTargetStillAliveAndTargetableVisitor stillAliveVisitor; + private int castStartTick = 0; + private boolean doneEffect = false; + + public CBehaviorHolyLight(final CUnit unit, final CAbilityHolyLight ability) { + super(unit); + this.ability = ability; + this.stillAliveVisitor = new AbilityTargetStillAliveAndTargetableVisitor(); + } + + public CBehaviorHolyLight reset(final CWidget target) { + innerReset(target, false); + this.castStartTick = 0; + this.doneEffect = false; + return this; + } + + @Override + public boolean isWithinRange(final CSimulation simulation) { + final float castRange = this.ability.getCastRange(); + return this.unit.canReach(this.target, castRange); + } + + @Override + protected CBehavior update(final CSimulation simulation, final boolean withinFacingWindow) { + this.unit.getUnitAnimationListener().playAnimation(false, null, SequenceUtils.SPELL, 1.0f, true); + if (this.castStartTick == 0) { + this.castStartTick = simulation.getGameTurnTick(); + } + final int ticksSinceCast = simulation.getGameTurnTick() - this.castStartTick; + final int castPointTicks = (int) (this.unit.getUnitType().getCastPoint() + / WarsmashConstants.SIMULATION_STEP_TIME); + final int backswingTicks = (int) (this.unit.getUnitType().getCastBackswingPoint() + / WarsmashConstants.SIMULATION_STEP_TIME); + if (!this.doneEffect && ((ticksSinceCast >= castPointTicks) || (ticksSinceCast >= backswingTicks))) { + this.doneEffect = true; + if (this.unit.getMana() >= this.ability.getManaCost()) { + this.unit.setMana(this.unit.getMana() - this.ability.getManaCost()); + } + else { + simulation.getCommandErrorListener().showNoManaError(this.unit.getPlayerIndex()); + return this.unit.pollNextOrderBehavior(simulation); + } + if (this.target instanceof CUnit) { + final CUnit targetUnit = (CUnit) this.target; + final boolean undead = targetUnit.getClassifications().contains(CUnitClassification.UNDEAD); + if (undead) { + targetUnit.damage(simulation, this.unit, CAttackType.SPELLS, CWeaponSoundTypeJass.WHOKNOWS.name(), + this.ability.getHealAmount() / 2); + } + else { + float newLifeValue = targetUnit.getLife() + this.ability.getHealAmount(); + if (newLifeValue > targetUnit.getMaxLife()) { + newLifeValue = targetUnit.getMaxLife(); + } + targetUnit.setLife(simulation, newLifeValue); + } + this.ability.setCooldownRemaining(this.ability.getCooldown()); + this.unit.fireCooldownsChangedEvent(); + simulation.createSpellEffectOnUnit(targetUnit, this.ability.getAlias()); + } + } + if (ticksSinceCast >= backswingTicks) { + return this.unit.pollNextOrderBehavior(simulation); + } + return this; + } + + @Override + protected CBehavior updateOnInvalidTarget(final CSimulation simulation) { + return this.unit.pollNextOrderBehavior(simulation); + } + + @Override + protected boolean checkTargetStillValid(final CSimulation simulation) { + return this.target.visit(this.stillAliveVisitor.reset(simulation, this.unit, this.ability.getTargetsAllowed())); + } + + @Override + protected void resetBeforeMoving(final CSimulation simulation) { + } + + @Override + public void begin(final CSimulation game) { + } + + @Override + public void end(final CSimulation game, final boolean interrupted) { + } + + @Override + public void endMove(final CSimulation game, final boolean interrupted) { + } + + @Override + public int getHighlightOrderId() { + return OrderIds.repair; + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java index 26f94af..6a7e7af 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CAbilityData.java @@ -6,8 +6,9 @@ import java.util.Map; import com.etheller.warsmash.units.manager.MutableObjectData; import com.etheller.warsmash.units.manager.MutableObjectData.MutableGameObject; import com.etheller.warsmash.util.War3ID; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CSimulation; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbility; -import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGenericDoNothing; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.CAbilityType; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.CAbilityTypeDefinition; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionCarrionSwarmDummy; @@ -17,6 +18,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.def import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionGoldMine; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvest; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHarvestLumber; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHolyLight; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionHumanRepair; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionInventory; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionInvulnerable; @@ -30,6 +32,8 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.def import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.types.definitions.impl.CAbilityTypeDefinitionWispHarvest; public class CAbilityData { + private static final War3ID REQUIRED_LEVEL = War3ID.fromString("arlv"); + private static final War3ID REQUIRED_LEVEL_SKIP = War3ID.fromString("alsk"); private final MutableObjectData abilityData; private Map> aliasToAbilityType = new HashMap<>(); @@ -42,7 +46,11 @@ public class CAbilityData { } private void registerCodes() { - this.codeToAbilityTypeDefinition.put(War3ID.fromString("ACcw"), new CAbilityTypeDefinitionColdArrows()); + // ----Human---- + // Paladin: + this.codeToAbilityTypeDefinition.put(War3ID.fromString("AHhb"), new CAbilityTypeDefinitionHolyLight()); + + this.codeToAbilityTypeDefinition.put(War3ID.fromString("AHca"), new CAbilityTypeDefinitionColdArrows()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Agld"), new CAbilityTypeDefinitionGoldMine()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Artn"), new CAbilityTypeDefinitionReturnResources()); this.codeToAbilityTypeDefinition.put(War3ID.fromString("Ahar"), new CAbilityTypeDefinitionHarvest()); @@ -87,12 +95,26 @@ public class CAbilityData { return abilityType; } + public int getHeroRequiredLevel(final CSimulation game, final War3ID alias, final int currentLevelOfAbility) { + // TODO maybe use CAbilityType for this to avoid hashtable lookups and just do + // fast symbol table resolution. + // (i.e. like all other fields of CAbilityType). For now I didn't bother because + // I wanted to just have this working. + final MutableGameObject mutableGameObject = this.abilityData.get(alias); + int levelSkip = mutableGameObject.getFieldAsInteger(REQUIRED_LEVEL_SKIP, 0); + if (levelSkip == 0) { + levelSkip = game.getGameplayConstants().getHeroAbilityLevelSkip(); + } + final int baseRequiredLevel = mutableGameObject.getFieldAsInteger(REQUIRED_LEVEL, 0); + return baseRequiredLevel + (currentLevelOfAbility * levelSkip); + } + public CAbility createAbility(final String ability, final int handleId) { final War3ID war3Id = War3ID.fromString(ability.trim()); final CAbilityType abilityType = getAbilityType(war3Id); if (abilityType != null) { return abilityType.createAbility(handleId); } - return new CAbilityGeneric(war3Id, handleId); + return new CAbilityGenericDoNothing(war3Id, handleId); } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java index ef5dd7d..316d110 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/data/CUnitData.java @@ -53,6 +53,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.util.SimulationRend public class CUnitData { private static final War3ID MANA_INITIAL_AMOUNT = War3ID.fromString("umpi"); private static final War3ID MANA_MAXIMUM = War3ID.fromString("umpm"); + private static final War3ID MANA_REGEN = War3ID.fromString("umpr"); private static final War3ID HIT_POINT_MAXIMUM = War3ID.fromString("uhpm"); private static final War3ID HIT_POINT_REGEN = War3ID.fromString("uhpr"); private static final War3ID HIT_POINT_REGEN_TYPE = War3ID.fromString("uhrt"); @@ -125,6 +126,9 @@ public class CUnitData { private static final War3ID ATTACK2_WEAPON_SOUND = War3ID.fromString("ucs2"); private static final War3ID ATTACK2_WEAPON_TYPE = War3ID.fromString("ua2w"); + private static final War3ID CAST_BACKSWING_POINT = War3ID.fromString("ucbs"); + private static final War3ID CAST_POINT = War3ID.fromString("ucpt"); + private static final War3ID ACQUISITION_RANGE = War3ID.fromString("uacq"); private static final War3ID MINIMUM_ATTACK_RANGE = War3ID.fromString("uamn"); @@ -153,7 +157,11 @@ public class CUnitData { private static final War3ID UNIT_RACE = War3ID.fromString("urac"); private static final War3ID REQUIRES = War3ID.fromString("ureq"); - private static final War3ID REQUIRES_AMOUNT = War3ID.fromString("urqc"); + private static final War3ID REQUIRES_AMOUNT = War3ID.fromString("urqa"); + private static final War3ID REQUIRES_TIER_COUNT = War3ID.fromString("urqc"); + private static final War3ID[] REQUIRES_TIER_X = { War3ID.fromString("urq1"), War3ID.fromString("urq2"), + War3ID.fromString("urq3"), War3ID.fromString("urq4"), War3ID.fromString("urq5"), War3ID.fromString("urq6"), + War3ID.fromString("urq7"), War3ID.fromString("urq8"), War3ID.fromString("urq9") }; private static final War3ID GOLD_COST = War3ID.fromString("ugol"); private static final War3ID LUMBER_COST = War3ID.fromString("ulum"); @@ -291,6 +299,7 @@ public class CUnitData { .parseRegenType(unitType.getFieldAsString(HIT_POINT_REGEN_TYPE, 0)); final int manaInitial = unitType.getFieldAsInteger(MANA_INITIAL_AMOUNT, 0); final int manaMaximum = unitType.getFieldAsInteger(MANA_MAXIMUM, 0); + final float manaRegen = unitType.getFieldAsFloat(MANA_REGEN, 0); final int speed = unitType.getFieldAsInteger(MOVEMENT_SPEED_BASE, 0); final int defense = unitType.getFieldAsInteger(DEFENSE, 0); final String abilityList = unitType.getFieldAsString(ABILITIES_NORMAL, 0); @@ -472,6 +481,9 @@ public class CUnitData { final int foodUsed = unitType.getFieldAsInteger(FOOD_USED, 0); final int foodMade = unitType.getFieldAsInteger(FOOD_MADE, 0); + final float castBackswingPoint = unitType.getFieldAsFloat(CAST_BACKSWING_POINT, 0); + final float castPoint = unitType.getFieldAsFloat(CAST_POINT, 0); + final int pointValue = unitType.getFieldAsInteger(POINT_VALUE, 0); final boolean revivesHeroes = unitType.getFieldAsBoolean(REVIVES_HEROES, 0); @@ -522,36 +534,14 @@ public class CUnitData { final String requirementsString = unitType.getFieldAsString(REQUIRES, 0); final String requirementsLevelsString = unitType.getFieldAsString(REQUIRES_AMOUNT, 0); - final String[] requirementsStringItems = requirementsString.split(","); - final String[] requirementsLevelsStringItems = requirementsLevelsString.split(","); - final List requirements = new ArrayList<>(); - for (int i = 0; i < requirementsStringItems.length; i++) { - final String item = requirementsStringItems[i]; - if (!item.isEmpty() && (item.length() == 4)) { - int level; - if (i < requirementsLevelsStringItems.length) { - if (requirementsLevelsStringItems[i].isEmpty()) { - level = 1; - } - else { - level = Integer.parseInt(requirementsLevelsStringItems[i]); - } - } - else if (requirementsLevelsStringItems.length > 0) { - final String requirementLevel = requirementsLevelsStringItems[requirementsLevelsStringItems.length - - 1]; - if (requirementLevel.isEmpty()) { - level = 1; - } - else { - level = Integer.parseInt(requirementLevel); - } - } - else { - level = 1; - } - requirements.add(new CUnitTypeRequirement(War3ID.fromString(item), level)); - } + final List requirements = parseRequirements(requirementsString, + requirementsLevelsString); + final int requirementsTiersCount = unitType.getFieldAsInteger(REQUIRES_TIER_COUNT, 0); + final List> requirementTiers = new ArrayList<>(); + for (int i = 1; i <= requirementsTiersCount; i++) { + final String requirementsTierString = unitType.getFieldAsString(REQUIRES_TIER_X[i - 1], 0); + final List tierRequirements = parseRequirements(requirementsTierString, ""); + requirementTiers.add(tierRequirements); } final EnumSet preventedPathingTypes = CBuildingPathingType @@ -566,31 +556,59 @@ public class CUnitData { final List heroProperNames = Arrays.asList(properNames.split(",")); - unitTypeInstance = new CUnitType(unitName, legacyName, typeId, life, lifeRegen, lifeRegenType, manaInitial, - manaMaximum, speed, defense, abilityList, isBldg, movementType, moveHeight, collisionSize, - classifications, attacks, armorType, raise, decay, defenseType, impactZ, buildingPathingPixelMap, - deathTime, targetedAs, acquisitionRange, minimumAttackRange, structuresBuilt, unitsTrained, - researchesAvailable, upgradesTo, unitRace, goldCost, lumberCost, foodUsed, foodMade, buildTime, - preventedPathingTypes, requiredPathingTypes, propWindow, turnRate, requirements, unitLevel, hero, - strength, strPlus, agility, agiPlus, intelligence, intPlus, primaryAttribute, heroAbilityList, - heroProperNames, properNamesCount, canFlee, priority, revivesHeroes, pointValue); + unitTypeInstance = new CUnitType(unitName, legacyName, typeId, life, lifeRegen, manaRegen, lifeRegenType, + manaInitial, manaMaximum, speed, defense, abilityList, isBldg, movementType, moveHeight, + collisionSize, classifications, attacks, armorType, raise, decay, defenseType, impactZ, + buildingPathingPixelMap, deathTime, targetedAs, acquisitionRange, minimumAttackRange, + structuresBuilt, unitsTrained, researchesAvailable, upgradesTo, unitRace, goldCost, lumberCost, + foodUsed, foodMade, buildTime, preventedPathingTypes, requiredPathingTypes, propWindow, turnRate, + requirements, requirementTiers, unitLevel, hero, strength, strPlus, agility, agiPlus, intelligence, + intPlus, primaryAttribute, heroAbilityList, heroProperNames, properNamesCount, canFlee, priority, + revivesHeroes, pointValue, castBackswingPoint, castPoint); this.unitIdToUnitType.put(typeId, unitTypeInstance); this.jassLegacyNameToUnitId.put(legacyName, typeId); } return unitTypeInstance; } + public List parseRequirements(final String requirementsString, + final String requirementsLevelsString) { + final String[] requirementsStringItems = requirementsString.split(","); + final String[] requirementsLevelsStringItems = requirementsLevelsString.split(","); + final List requirements = new ArrayList<>(); + for (int i = 0; i < requirementsStringItems.length; i++) { + final String item = requirementsStringItems[i]; + if (!item.isEmpty() && (item.length() == 4)) { + int level; + if (i < requirementsLevelsStringItems.length) { + if (requirementsLevelsStringItems[i].isEmpty()) { + level = 1; + } + else { + level = Integer.parseInt(requirementsLevelsStringItems[i]); + } + } + else if (requirementsLevelsStringItems.length > 0) { + final String requirementLevel = requirementsLevelsStringItems[requirementsLevelsStringItems.length + - 1]; + if (requirementLevel.isEmpty()) { + level = 1; + } + else { + level = Integer.parseInt(requirementLevel); + } + } + else { + level = 1; + } + requirements.add(new CUnitTypeRequirement(War3ID.fromString(item), level)); + } + } + return requirements; + } + private String getLegacyName(final MutableGameObject unitType) { - String legacyName; - if (unitType.isCustom()) { - legacyName = "custom_" + unitType.getAlias(); - } - else { - // ?? this might be correct here, not sure, legacy name is mostly only used - // for spawning hidden units in campaign secrets - legacyName = unitType.readSLKTag("name"); - } - return legacyName; + return unitType.getLegacyName(); } private static int[] populateHeroStatTable(final int maxHeroLevel, final float statPerLevel) { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java index 0df6376..a5edb27 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/pathing/CPathfindingProcessor.java @@ -391,13 +391,16 @@ public class CPathfindingProcessor { } } if (stepsBackward > this.pathingGridCellCount) { - throw new IllegalStateException( + new IllegalStateException( "PATHING SYSTEM ERROR: The path finding algorithm hit an infinite cycle at or near pt: " + current.cameFrom.point + ".\nThis means the A* search algorithm heuristic 'admissable' constraint was probably violated.\n\nUnit1:" + CUnit.maybeMeaningfulName(job.ignoreIntersectionsWithThisUnit) + "\nUnit2:" - + CUnit.maybeMeaningfulName(job.ignoreIntersectionsWithThisSecondUnit)); + + CUnit.maybeMeaningfulName(job.ignoreIntersectionsWithThisSecondUnit)) + .printStackTrace(); + totalPath.clear(); + break; } stepsBackward++; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java index e7df2e9..1371c84 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayer.java @@ -273,6 +273,21 @@ public class CPlayer extends CBasePlayer { return this.heroes; } + public int getHeroCount(final CSimulation game, final boolean includeInProgress) { + if (!includeInProgress) { + return this.heroes.size(); + } + else { + int heroInProgressCount = 0; + for (final Map.Entry entry : this.rawcodeToTechtreeInProgress.entrySet()) { + if (game.getUnitData().getUnitType(entry.getKey()).isHero()) { + heroInProgressCount += entry.getValue(); + } + } + return this.heroes.size() + heroInProgressCount; + } + } + public void fireHeroLevelEvents(final CUnit hero) { firePlayerUnitEvents(hero, CommonTriggerExecutionScope::playerHeroRevivableScope, JassGameEventsWar3.EVENT_PLAYER_HERO_LEVEL); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListenerDelaying.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListenerDelaying.java new file mode 100644 index 0000000..2529d69 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/players/CPlayerUnitOrderListenerDelaying.java @@ -0,0 +1,70 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.simulation.players; + +import java.util.ArrayList; +import java.util.List; + +public class CPlayerUnitOrderListenerDelaying implements CPlayerUnitOrderListener { + private final CPlayerUnitOrderListener delegate; + private final List actions = new ArrayList<>(); + + public CPlayerUnitOrderListenerDelaying(final CPlayerUnitOrderListener delegate) { + this.delegate = delegate; + } + + @Override + public void issueTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetHandleId, final boolean queue) { + this.actions.add(() -> { + this.delegate.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); + }); + } + + @Override + public void issuePointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, final float x, + final float y, final boolean queue) { + this.actions.add(() -> { + this.delegate.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue); + }); + } + + @Override + public void issueDropItemAtPointOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetHandleId, final float x, final float y, final boolean queue) { + this.actions.add(() -> { + this.delegate.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, + queue); + }); + } + + @Override + public void issueDropItemAtTargetOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final int targetItemHandleId, final int targetHeroHandleId, final boolean queue) { + this.actions.add(() -> { + this.delegate.issueDropItemAtTargetOrder(unitHandleId, abilityHandleId, orderId, targetItemHandleId, + targetHeroHandleId, queue); + }); + } + + @Override + public void issueImmediateOrder(final int unitHandleId, final int abilityHandleId, final int orderId, + final boolean queue) { + this.actions.add(() -> { + this.delegate.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); + }); + } + + @Override + public void unitCancelTrainingItem(final int unitHandleId, final int cancelIndex) { + this.actions.add(() -> { + this.delegate.unitCancelTrainingItem(unitHandleId, cancelIndex); + }); + } + + public void publishDelayedActions() { + for (final Runnable action : this.actions) { + action.run(); + } + this.actions.clear(); + } + +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java index a49a09e..8f80711 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityActivationReceiver.java @@ -11,6 +11,10 @@ public interface AbilityActivationReceiver { void missingRequirement(War3ID type, int level); + void missingHeroLevelRequirement(int level); + + void noHeroSkillPointsAvailable(); + void casterMovementDisabled(); void cargoCapacityUnavailable(); @@ -18,4 +22,6 @@ public interface AbilityActivationReceiver { void disabled(); void techtreeMaximumReached(); + + void cooldownNotYetReady(float cooldownRemaining, float cooldown); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java index 3ffc46e..ca3b244 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/AbilityTargetCheckReceiver.java @@ -13,6 +13,12 @@ public interface AbilityTargetCheckReceiver { void notAnActiveAbility(); + void notHolyBoltTarget(); + + void alreadyFullHealth(); + + void notDeathCoilTarget(); + void targetNotVisible(); void targetTooComplicated(); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java index fdee4fe..2740bea 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityActivationReceiver.java @@ -26,6 +26,16 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv this.ok = false; } + @Override + public void missingHeroLevelRequirement(final int level) { + this.ok = false; + } + + @Override + public void noHeroSkillPointsAvailable() { + this.ok = false; + } + @Override public void techtreeMaximumReached() { this.ok = false; @@ -46,6 +56,11 @@ public class BooleanAbilityActivationReceiver implements AbilityActivationReceiv this.ok = false; } + @Override + public void cooldownNotYetReady(final float cooldownRemaining, final float cooldownMax) { + this.ok = false; + } + public boolean isOk() { return this.ok; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java index eb71456..e79abf1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/BooleanAbilityTargetCheckReceiver.java @@ -48,6 +48,21 @@ public final class BooleanAbilityTargetCheckReceiver implements Abi this.targetable = false; } + @Override + public void notHolyBoltTarget() { + this.targetable = false; + } + + @Override + public void alreadyFullHealth() { + this.targetable = false; + } + + @Override + public void notDeathCoilTarget() { + this.targetable = false; + } + @Override public void targetNotVisible() { this.targetable = false; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java index 63afc87..22ee3db 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/CWidgetAbilityTargetCheckReceiver.java @@ -42,6 +42,21 @@ public class CWidgetAbilityTargetCheckReceiver implements AbilityTargetCheckRece this.target = null; } + @Override + public void notHolyBoltTarget() { + this.target = null; + } + + @Override + public void alreadyFullHealth() { + this.target = null; + } + + @Override + public void notDeathCoilTarget() { + this.target = null; + } + @Override public void targetNotVisible() { this.target = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java index 24e1947..f4f6de0 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/MeleeUIAbilityActivationReceiver.java @@ -9,7 +9,9 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv private final AbilityActivationErrorHandler noGoldError; private final AbilityActivationErrorHandler noLumberError; private final AbilityActivationErrorHandler noFoodError; + private final AbilityActivationErrorHandler noManaError; private final AbilityActivationErrorHandler genericError; + private final AbilityActivationErrorHandler cooldownError; private boolean ok = false; private CommandErrorListener commandErrorListener; @@ -18,11 +20,14 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv public MeleeUIAbilityActivationReceiver(final AbilityActivationErrorHandler noGoldError, final AbilityActivationErrorHandler noLumberError, final AbilityActivationErrorHandler noFoodError, - final AbilityActivationErrorHandler genericError) { + final AbilityActivationErrorHandler noManaError, final AbilityActivationErrorHandler genericError, + final AbilityActivationErrorHandler cooldownError) { this.noGoldError = noGoldError; this.noLumberError = noLumberError; this.noFoodError = noFoodError; + this.noManaError = noManaError; this.genericError = genericError; + this.cooldownError = cooldownError; } public MeleeUIAbilityActivationReceiver reset(final CommandErrorListener commandErrorListener, @@ -51,9 +56,17 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv case FOOD: this.noFoodError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); break; + case MANA: + this.noManaError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + break; } } + @Override + public void cooldownNotYetReady(final float cooldownRemaining, final float cooldown) { + this.cooldownError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + @Override public void notAnActiveAbility() { this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); @@ -64,6 +77,16 @@ public class MeleeUIAbilityActivationReceiver implements AbilityActivationReceiv this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); } + @Override + public void missingHeroLevelRequirement(final int level) { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + + @Override + public void noHeroSkillPointsAvailable() { + this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); + } + @Override public void techtreeMaximumReached() { this.genericError.onClick(this.commandErrorListener, this.worldSceneAudioContext, this.commandedUnit); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java index ed0f9fd..5bc9cd2 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/PointAbilityTargetCheckReceiver.java @@ -32,6 +32,21 @@ public class PointAbilityTargetCheckReceiver implements AbilityTargetCheckReceiv this.target = null; } + @Override + public void notHolyBoltTarget() { + this.target = null; + } + + @Override + public void alreadyFullHealth() { + this.target = null; + } + + @Override + public void notDeathCoilTarget() { + this.target = null; + } + @Override public void targetOutsideRange() { this.target = null; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java index 6e7977c..ec00a5f 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/ResourceType.java @@ -3,7 +3,8 @@ package com.etheller.warsmash.viewer5.handlers.w3x.simulation.util; public enum ResourceType { GOLD, LUMBER, - FOOD; + FOOD, + MANA; public static final ResourceType[] VALUES = values(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java index 129ae5b..f039ef1 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/SimulationRenderController.java @@ -77,5 +77,10 @@ public interface SimulationRenderController { void heroDeathEvent(CUnit cUnit); - SimulationRenderComponent createSpellEffectOverDestructable(CUnit source, CDestructable target, War3ID alias, float artAttachmentHeight); + SimulationRenderComponent createSpellEffectOverDestructable(CUnit source, CDestructable target, War3ID alias, + float artAttachmentHeight); + + void unitUpgradingEvent(CUnit unit, War3ID upgradeIdType); + + void unitCancelUpgradingEvent(CUnit unit, War3ID upgradeIdType); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java index 2e8280a..ab93597 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgAbilityActivationReceiver.java @@ -40,6 +40,21 @@ public class StringMsgAbilityActivationReceiver implements AbilityActivationRece this.message = "NOTEXTERN: Requires " + type; } + @Override + public void missingHeroLevelRequirement(final int level) { + this.message = "NOTEXTERN: Requires Level " + level; + } + + @Override + public void noHeroSkillPointsAvailable() { + this.message = "NOTEXTERN: No hero skill points available."; + } + + @Override + public void cooldownNotYetReady(final float cooldownRemaining, final float cooldown) { + this.message = "NOTEXTERN: Spell is not ready yet."; + } + @Override public void techtreeMaximumReached() { this.message = "NOTEXTERN: Techtree maximum reached."; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java index 5ef87ec..1711fc3 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/util/StringMsgTargetCheckReceiver.java @@ -63,6 +63,21 @@ public final class StringMsgTargetCheckReceiver implements AbilityT } } + @Override + public void notHolyBoltTarget() { + this.message = "NOTEXTERN: Holybolttarget=Must target friendly living units or enemy Undead units."; + } + + @Override + public void alreadyFullHealth() { + this.message = "NOTEXTERN: Target is already full health!"; + } + + @Override + public void notDeathCoilTarget() { + this.message = "NOTEXTERN: Deathcoiltarget=Must target enemy living units or friendly Undead units."; + } + @Override public void mustTargetResources() { this.message = "NOTEXTERN: Must target resources."; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java index e7989c1..c6fe1aa 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/CommandCardIcon.java @@ -8,6 +8,7 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.utils.viewport.Viewport; import com.etheller.warsmash.parsers.fdf.GameUI; import com.etheller.warsmash.parsers.fdf.frames.AbstractRenderableFrame; +import com.etheller.warsmash.parsers.fdf.frames.SingleStringFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; import com.etheller.warsmash.parsers.fdf.frames.UIFrame; @@ -21,12 +22,15 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl private TextureFrame activeHighlightFrame; private SpriteFrame cooldownFrame; private SpriteFrame autocastFrame; + private TextureFrame numberOverlayFrame; + private SingleStringFrame numberOverlayStringFrame; private float defaultWidth; private float defaultHeight; private int abilityHandleId; private int orderId; private int autoCastOrderId; private boolean autoCastActive; + private boolean cooldownActive; private final CommandCardCommandListener commandCardCommandListener; private boolean menuButton; private String tip; @@ -34,6 +38,7 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl private int tipGoldCost; private int tipLumberCost; private int tipFoodCost; + private int tipManaCost; private char hotkey; public CommandCardIcon(final String name, final UIFrame parent, @@ -43,11 +48,14 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl } public void set(final TextureFrame iconFrame, final TextureFrame activeHighlightFrame, - final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame) { + final SpriteFrame cooldownFrame, final SpriteFrame autocastFrame, final TextureFrame numberOverlayFrame, + final SingleStringFrame numberOverlayStringFrame) { this.iconFrame = iconFrame; this.activeHighlightFrame = activeHighlightFrame; this.cooldownFrame = cooldownFrame; this.autocastFrame = autocastFrame; + this.numberOverlayFrame = numberOverlayFrame; + this.numberOverlayStringFrame = numberOverlayStringFrame; } public void clear() { @@ -59,6 +67,10 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl if (this.autocastFrame != null) { this.autocastFrame.setVisible(false); } + if (this.numberOverlayFrame != null) { + this.numberOverlayFrame.setVisible(false); + this.numberOverlayStringFrame.setVisible(false); + } setVisible(false); this.hotkey = '\0'; } @@ -66,7 +78,8 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl public void setCommandButtonData(final Texture texture, final int abilityHandleId, final int orderId, final int autoCastOrderId, final boolean active, final boolean autoCastActive, final boolean menuButton, final String tip, final String uberTip, final char hotkey, final int goldCost, final int lumberCost, - final int foodCost) { + final int foodCost, final int manaCost, final boolean notEnoughMana, final float cooldownRemaining, + final float cooldownMax, final int numberOverlay) { this.menuButton = menuButton; this.hotkey = hotkey; setVisible(true); @@ -74,7 +87,12 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl if (this.activeHighlightFrame != null) { this.activeHighlightFrame.setVisible(active); } - this.cooldownFrame.setVisible(false); + if (this.numberOverlayFrame != null) { + final boolean useNumberOverlay = numberOverlay != -1; + this.numberOverlayFrame.setVisible(useNumberOverlay); + this.numberOverlayStringFrame.setVisible(useNumberOverlay); + this.numberOverlayStringFrame.setText(Integer.toString(numberOverlay)); + } if (this.autocastFrame != null) { this.autocastFrame.setVisible(autoCastOrderId != 0); if (autoCastOrderId != 0) { @@ -89,6 +107,17 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.autoCastActive = autoCastActive; } } + if (cooldownRemaining > 0) { + this.cooldownFrame.setVisible(true); + this.cooldownFrame.setAnimationSpeed(1.0f / cooldownMax); + this.cooldownFrame.setSequence(PrimaryTag.STAND); + this.cooldownFrame.setFrameByRatio(1.0f - (cooldownRemaining / cooldownMax)); + this.cooldownActive = true; + } + else { + this.cooldownFrame.setVisible(false); + this.cooldownActive = false; + } this.iconFrame.setTexture(texture); this.abilityHandleId = abilityHandleId; this.orderId = orderId; @@ -98,11 +127,24 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl this.tipGoldCost = goldCost; this.tipLumberCost = lumberCost; this.tipFoodCost = foodCost; + this.tipManaCost = manaCost; + if (notEnoughMana) { + this.iconFrame.setColor(0.3f, 0.5f, 1f, 1f); + } + else { + this.iconFrame.setColor(1f, 1f, 1f, 1f); + } } @Override protected void innerPositionBounds(final GameUI gameUI, final Viewport viewport) { this.iconFrame.positionBounds(gameUI, viewport); + if (this.numberOverlayFrame != null) { + this.numberOverlayFrame.positionBounds(gameUI, viewport); + } + if (this.numberOverlayStringFrame != null) { + this.numberOverlayStringFrame.positionBounds(gameUI, viewport); + } if (this.activeHighlightFrame != null) { this.activeHighlightFrame.positionBounds(gameUI, viewport); } @@ -115,10 +157,21 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl @Override protected void internalRender(final SpriteBatch batch, final BitmapFont baseFont, final GlyphLayout glyphLayout) { this.iconFrame.render(batch, baseFont, glyphLayout); + if (this.numberOverlayFrame != null) { + this.numberOverlayFrame.render(batch, baseFont, glyphLayout); + } + if (this.numberOverlayStringFrame != null) { + this.numberOverlayStringFrame.render(batch, baseFont, glyphLayout); + } if (this.activeHighlightFrame != null) { this.activeHighlightFrame.render(batch, baseFont, glyphLayout); } this.cooldownFrame.render(batch, baseFont, glyphLayout); + if (this.cooldownFrame.isVisible() && this.cooldownFrame.isSequenceEnded() && this.cooldownActive) { + this.cooldownFrame.setAnimationSpeed(1.0f); + this.cooldownFrame.setSequence(PrimaryTag.DEATH); + this.cooldownActive = false; + } if (this.autocastFrame != null) { this.autocastFrame.render(batch, baseFont, glyphLayout); } @@ -172,7 +225,7 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl } @Override - public void mouseDragged(GameUI rootFrame, Viewport uiViewport, float x, float y) { + public void mouseDragged(final GameUI rootFrame, final Viewport uiViewport, final float x, final float y) { } @@ -242,4 +295,9 @@ public class CommandCardIcon extends AbstractRenderableFrame implements Clickabl public int getToolTipFoodCost() { return this.tipFoodCost; } + + @Override + public int getToolTipManaCost() { + return this.tipManaCost; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 3448a5c..8e5481a 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -50,6 +50,7 @@ import com.etheller.warsmash.parsers.fdf.frames.SetPoint; import com.etheller.warsmash.parsers.fdf.frames.SimpleButtonFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; import com.etheller.warsmash.parsers.fdf.frames.SimpleStatusBarFrame; +import com.etheller.warsmash.parsers.fdf.frames.SingleStringFrame; import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; import com.etheller.warsmash.parsers.fdf.frames.StringFrame; import com.etheller.warsmash.parsers.fdf.frames.TextureFrame; @@ -116,7 +117,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidget; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.CWidgetFilterFunction; 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.CAbilityGeneric; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityGenericDoNothing; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityMove; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityView; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.CAbilityVisitor; @@ -131,6 +132,7 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.build.CAb import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.combat.CAbilityColdArrows; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericNoIconAbility; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.generic.GenericSingleIconActiveAbility; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.harvest.CAbilityReturnResources; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CAbilityHero; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.hero.CPrimaryAttribute; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.abilities.inventory.CAbilityInventory; @@ -940,7 +942,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); cooldownFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_INVENTORY_ICON_WIDTH)); - commandCardIcon.set(iconFrame, null, cooldownFrame, null); + commandCardIcon.set(iconFrame, null, cooldownFrame, null, null, null); this.inventoryIcons[j][i] = commandCardIcon; commandCardIcon.clear(); commandButtonIndex++; @@ -999,6 +1001,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final FilterModeTextureFrame activeHighlightFrame = new FilterModeTextureFrame( "SmashCommandButton_" + (commandButtonIndex) + "_ActiveHighlight", this.rootFrame, true, null); activeHighlightFrame.setFilterMode(FilterMode.ADDALPHA); + final TextureFrame numberOverlayFrame = new TextureFrame( + "SmashCommandButton_" + (commandButtonIndex) + "_NumberOverlay", this.rootFrame, true, null); final SpriteFrame cooldownFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCommandButton_" + (commandButtonIndex) + "_Cooldown", this.rootFrame, "", 0); final SpriteFrame autocastFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", @@ -1017,6 +1021,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma activeHighlightFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); activeHighlightFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); activeHighlightFrame.setTexture("CommandButtonActiveHighlight", this.rootFrame); + + numberOverlayFrame.addSetPoint( + new SetPoint(FramePoint.BOTTOMRIGHT, commandCardIcon, FramePoint.BOTTOMRIGHT, 0, 0)); + numberOverlayFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH) * 0.4f); + numberOverlayFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH) * 0.4f); + numberOverlayFrame.setTexture("CommandButtonNumberOverlay", this.rootFrame); + final SingleStringFrame numberOverlayStringFrame = new SingleStringFrame( + "SmashCommandButton_NumberOverlayText", numberOverlayFrame, Color.WHITE, TextJustify.CENTER, + TextJustify.BOTTOM, this.rootFrame.getFont()); +// numberOverlayStringFrame.addAnchor(new AnchorDefinition(FramePoint.CENTER, 0, 0)); + numberOverlayStringFrame.setSetAllPoints(true); cooldownFrame.addSetPoint(new SetPoint(FramePoint.CENTER, commandCardIcon, FramePoint.CENTER, 0, 0)); this.rootFrame.setSpriteFrameModel(cooldownFrame, this.rootFrame.getSkinField("CommandButtonCooldown")); cooldownFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); @@ -1025,7 +1040,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.setSpriteFrameModel(autocastFrame, this.rootFrame.getSkinField("CommandButtonAutocast")); autocastFrame.setWidth(GameUI.convertX(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); autocastFrame.setHeight(GameUI.convertY(this.uiViewport, DEFAULT_COMMAND_CARD_ICON_WIDTH)); - commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame); + commandCardIcon.set(iconFrame, activeHighlightFrame, cooldownFrame, autocastFrame, numberOverlayFrame, + numberOverlayStringFrame); this.commandCard[j][i] = commandCardIcon; commandCardIcon.clear(); commandButtonIndex++; @@ -1084,9 +1100,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma Gdx.input.setCursorCatched(true); } - this.meleeUIMinimap = - - createMinimap(this.war3MapViewer); + this.meleeUIMinimap = createMinimap(this.war3MapViewer); this.meleeUIAbilityActivationReceiver = new MeleeUIAbilityActivationReceiver( new AbilityActivationErrorHandler(this.war3MapViewer.getLocalPlayerIndex(), @@ -1098,7 +1112,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma new AbilityActivationErrorHandler(this.war3MapViewer.getLocalPlayerIndex(), this.rootFrame.getErrorString("NoFood"), this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoFoodSound"))), + new AbilityActivationErrorHandler(this.war3MapViewer.getLocalPlayerIndex(), + this.rootFrame.getErrorString("Nomana"), + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoManaSound"))), new AbilityActivationErrorHandler(this.war3MapViewer.getLocalPlayerIndex(), "", + this.war3MapViewer.getUiSounds().getSound("InterfaceError")), + new AbilityActivationErrorHandler(this.war3MapViewer.getLocalPlayerIndex(), + this.rootFrame.getErrorString("Cooldown"), this.war3MapViewer.getUiSounds().getSound("InterfaceError"))); final MdxModel rallyModel = this.war3MapViewer.loadModelMdx(this.rootFrame.getSkinField("RallyIndicatorDst")); @@ -1159,6 +1179,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final boolean shiftDown = isShiftDown(); this.unitOrderListener.issueImmediateOrder(this.selectedUnit.getSimulationUnit().getHandleId(), abilityHandleId, orderId, shiftDown); + if (abilityToUse instanceof CAbilityHero) { + if ((((CAbilityHero) abilityToUse).getSkillPoints() <= 1) && (orderId != OrderIds.skillmenu)) { + // using up the last skill point, so close the menu + // TODO this is kind of a stupid hack and should probably be improved later + openMenu(0); + } + } if (this.selectedUnits.size() > 1) { for (final RenderUnit otherSelectedUnit : this.selectedUnits) { if (otherSelectedUnit != this.activeCommandUnit) { @@ -1260,6 +1287,15 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void showNoManaError(final int playerIndex) { + if (playerIndex == this.war3MapViewer.getLocalPlayerIndex()) { + showCommandError(playerIndex, this.rootFrame.getErrorString("Nomana")); + this.war3MapViewer.getUiSounds().getSound(this.rootFrame.getSkinField("NoManaSound")) + .play(this.uiScene.audioContext, 0, 0, 0); + } + } + @Override public void showUnableToFindCoupleTargetError(final int playerIndex) { if (playerIndex == this.war3MapViewer.getLocalPlayerIndex()) { @@ -1744,7 +1780,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } @Override - public Void accept(final CAbilityGeneric ability) { + public Void accept(final CAbilityGenericDoNothing ability) { handleTargetCursor(ability); return null; } @@ -1804,6 +1840,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma return null; } + @Override + public Void accept(final CAbilityReturnResources ability) { + // this should probably never happen + handleTargetCursor(ability); + return null; + } + @Override public Void accept(final CAbilityRally ability) { handleTargetCursor(ability); @@ -2126,7 +2169,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma if (item == null) { if (index < inventory.getItemCapacity()) { inventoryIcon.setCommandButtonData(null, 0, 0, index + 1, true, false, false, null, null, - '\0', 0, 0, 0); + '\0', 0, 0, 0, 0, false, 0, 0, -1); } } index++; @@ -2458,7 +2501,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final String unitTypeName = simulationUnit.getUnitType().getName(); final boolean anyAttacks = simulationUnit.getAttacks().size() > 0; - final boolean constructing = simulationUnit.isConstructing(); + final boolean constructing = simulationUnit.isConstructingOrUpgrading(); final UIFrame localArmorIcon = this.armorIcon; final TextureFrame localArmorIconBackdrop = this.armorIconBackdrop; final StringFrame localArmorInfoPanelIconValue = this.armorInfoPanelIconValue; @@ -2563,11 +2606,16 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.simpleBuildTimeIndicator.setVisible(constructing); this.simpleBuildingBuildTimeIndicator.setVisible(false); if (constructing) { + War3ID constructingTypeId = simulationUnit.getTypeId(); + if (simulationUnit.isUpgrading()) { + constructingTypeId = simulationUnit.getUpgradeIdType(); + } + this.rootFrame.setText(this.simpleBuildingActionLabel, this.rootFrame.getTemplates().getDecoratedString("CONSTRUCTING")); this.queueIconFrames[0].setVisible(true); - this.queueIconFrames[0].setTexture( - this.war3MapViewer.getAbilityDataUI().getUnitUI(simulationUnit.getTypeId()).getIcon()); + this.queueIconFrames[0] + .setTexture(this.war3MapViewer.getAbilityDataUI().getUnitUI(constructingTypeId).getIcon()); if (simulationUnit.getWorkerInside() != null) { this.selectWorkerInsideFrame.setVisible(true); @@ -2639,17 +2687,17 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma inventoryIcon.setCommandButtonData(iconUI.getIcon(), 0, activelyUsed ? (OrderIds.itemuse00 + index) : 0, index + 1, activelyUsed, false, false, itemUI.getName(), this.recycleStringBuilder.toString(), '\0', itemType.getGoldCost(), - itemType.getLumberCost(), 0); + itemType.getLumberCost(), 0, 0, false, 0, 0, -1); } else { if (index >= inventory.getItemCapacity()) { inventoryIcon.setCommandButtonData(this.consoleInventoryNoCapacityTexture, 0, 0, 0, false, - false, false, null, null, '\0', 0, 0, 0); + false, false, null, null, '\0', 0, 0, 0, 0, false, 0, 0, -1); } else { if (this.draggingItem != null) { inventoryIcon.setCommandButtonData(null, 0, 0, index + 1, true, false, false, null, - null, '\0', 0, 0, 0); + null, '\0', 0, 0, 0, 0, false, 0, 0, -1); } else { inventoryIcon.clear(); @@ -2675,7 +2723,8 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma public void commandButton(final int buttonPositionX, final int buttonPositionY, final Texture icon, final int abilityHandleId, final int orderId, final int autoCastId, final boolean active, final boolean autoCastActive, final boolean menuButton, final String tip, final String uberTip, - final char hotkey, final int goldCost, final int lumberCost, final int foodCost) { + final char hotkey, final int goldCost, final int lumberCost, final int foodCost, final int manaCost, + final float cooldownRemaining, final float cooldownMax, final int numberOverlay) { int x = Math.max(0, Math.min(COMMAND_CARD_WIDTH - 1, buttonPositionX)); int y = Math.max(0, Math.min(COMMAND_CARD_HEIGHT - 1, buttonPositionY)); while ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT) && this.commandCard[y][x].isVisible()) { @@ -2687,7 +2736,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } if ((x < COMMAND_CARD_WIDTH) && (y < COMMAND_CARD_HEIGHT)) { this.commandCard[y][x].setCommandButtonData(icon, abilityHandleId, orderId, autoCastId, active, - autoCastActive, menuButton, tip, uberTip, hotkey, goldCost, lumberCost, foodCost); + autoCastActive, menuButton, tip, uberTip, hotkey, goldCost, lumberCost, foodCost, manaCost, + this.selectedUnit.getSimulationUnit().getMana() < manaCost, cooldownRemaining, cooldownMax, + numberOverlay); } } @@ -2769,6 +2820,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void manaChanged() { + this.rootFrame.setText(this.unitManaText, + FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getMana()) + " / " + + FastNumberFormat.formatWholeNumber(this.selectedUnit.getSimulationUnit().getMaximumMana())); + } + @Override public void goldChanged() { this.rootFrame.setText(this.resourceBarGoldText, Integer.toString(this.localPlayer.getGold())); @@ -2844,7 +2902,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final IconUI cancelUI = abilityDataUI.getCancelUI(); commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, menuOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), - cancelUI.getHotkey(), 0, 0, 0); + cancelUI.getHotkey(), 0, 0, 0, 0, 0, 0, -1); } else { if (menuOrderId != 0) { @@ -2854,7 +2912,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final IconUI cancelUI = abilityDataUI.getCancelUI(); commandButton(cancelUI.getButtonPositionX(), cancelUI.getButtonPositionY(), cancelUI.getIcon(), 0, exitOrderId, 0, false, false, true, cancelUI.getToolTip(), cancelUI.getUberTip(), - cancelUI.getHotkey(), 0, 0, 0); + cancelUI.getHotkey(), 0, 0, 0, 0, 0, 0, -1); } this.selectedUnit.populateCommandCard(this.war3MapViewer.simulation, this.rootFrame, this, abilityDataUI, menuOrderId, this.selectedUnits.size() > 1); @@ -3577,6 +3635,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma final int goldCost = mousedUIFrame.getToolTipGoldCost(); final int lumberCost = mousedUIFrame.getToolTipLumberCost(); final int foodCost = mousedUIFrame.getToolTipFoodCost(); + final int manaCost = mousedUIFrame.getToolTipManaCost(); final String toolTip = mousedUIFrame.getToolTip(); final String uberTip = mousedUIFrame.getUberTip(); if ((toolTip == null) || (uberTip == null)) { @@ -3603,6 +3662,12 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(foodCost)); resourceIndex++; } + if (manaCost != 0) { + this.tooltipResourceFrames[resourceIndex].setVisible(true); + this.tooltipResourceIconFrames[resourceIndex].setTexture("ToolTipManaIcon", this.rootFrame); + this.rootFrame.setText(this.tooltipResourceTextFrames[resourceIndex], Integer.toString(manaCost)); + resourceIndex++; + } for (int i = resourceIndex; i < this.tooltipResourceFrames.length; i++) { this.tooltipResourceFrames[i].setVisible(false); } @@ -3632,7 +3697,7 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void queueIconClicked(final int index) { final CUnit simulationUnit = this.selectedUnit.getSimulationUnit(); - if (simulationUnit.isConstructing()) { + if (simulationUnit.isConstructingOrUpgrading()) { switch (index) { case 0: for (final CAbility ability : simulationUnit.getAbilities()) { @@ -3754,6 +3819,13 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } } + @Override + public void manaChanged() { + MeleeUI.this.selectedUnitFrames[this.index] + .setManaRatioRemaining(this.sourceUnit.getSimulationUnit().getMana() + / this.sourceUnit.getSimulationUnit().getMaximumMana()); + } + @Override public void ordersChanged() { diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java index 0dae6b2..a41ba86 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MenuUI.java @@ -10,6 +10,7 @@ import java.util.Collections; import java.util.List; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.GlyphLayout; @@ -48,11 +49,13 @@ import com.etheller.warsmash.parsers.w3x.w3i.War3MapW3i; import com.etheller.warsmash.units.DataTable; import com.etheller.warsmash.units.Element; import com.etheller.warsmash.units.custom.WTS; +import com.etheller.warsmash.util.DataSourceFileHandle; import com.etheller.warsmash.util.StringBundle; import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WorldEditStrings; import com.etheller.warsmash.viewer5.Scene; import com.etheller.warsmash.viewer5.handlers.mdx.MdxViewer; +import com.etheller.warsmash.viewer5.handlers.w3x.AnimationTokens.PrimaryTag; import com.etheller.warsmash.viewer5.handlers.w3x.UnitSound; import com.etheller.warsmash.viewer5.handlers.w3x.War3MapViewer; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.CBasePlayer; @@ -60,12 +63,15 @@ import com.etheller.warsmash.viewer5.handlers.w3x.simulation.config.War3MapConfi import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CMapControl; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderExecutor; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListener; +import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CPlayerUnitOrderListenerDelaying; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.players.CRace; import com.etheller.warsmash.viewer5.handlers.w3x.simulation.trigger.enumtypes.CPlayerSlotState; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.ClickableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.command.FocusableFrame; import com.etheller.warsmash.viewer5.handlers.w3x.ui.mapsetup.MapInfoPane; import com.etheller.warsmash.viewer5.handlers.w3x.ui.mapsetup.TeamSetupPane; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.BattleNetUI; +import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.BattleNetUIActionListener; import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMenuData; import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMenuUI; import com.etheller.warsmash.viewer5.handlers.w3x.ui.menu.CampaignMission; @@ -94,6 +100,7 @@ public class MenuUI { private SpriteFrame glueSpriteLayerTopRight; private SpriteFrame glueSpriteLayerTopLeft; + private SpriteFrame glueSpriteLayerCenter; private WorldEditStrings worldEditStrings; @@ -151,13 +158,17 @@ public class MenuUI { private GlueTextButtonFrame campaignBackButton; private UIFrame missionSelectFrame; private UIFrame campaignSelectFrame; - private final DataTable campaignStrings; + private DataTable campaignStrings; private SpriteFrame campaignWarcraftIIILogo; private final SingleModelScreen menuScreen; private CampaignMenuData currentCampaign; private String[] campaignList; private CampaignMenuData[] campaignDatas; + + // BattleNet + private BattleNetUI battleNetUI; + private UnitSound mainMenuGlueScreenLoop; private GlueTextButtonFrame addProfileButton; private GlueTextButtonFrame deleteProfileButton; @@ -180,6 +191,10 @@ public class MenuUI { private boolean unifiedCampaignInfo = false; private MapInfoPane skirmishMapInfoPane; private War3MapConfig currentMapConfig; + private final DataTable musicSLK; + private Music[] currentMusics; + private int currentMusicIndex; + private boolean currentMusicRandomizeIndex; public MenuUI(final DataSource dataSource, final Viewport uiViewport, final Scene uiScene, final MdxViewer viewer, final WarsmashGdxMultiScreenGame screenManager, final SingleModelScreen menuScreen, @@ -196,28 +211,18 @@ public class MenuUI { this.widthRatioCorrection = getMinWorldWidth() / 1600f; this.heightRatioCorrection = getMinWorldHeight() / 1200f; - this.campaignStrings = new DataTable(StringBundle.EMPTY); - final String campaignStringPath = "UI\\CampaignStrings" + (WarsmashConstants.GAME_VERSION == 1 ? "_exp" : "") - + ".txt"; - if (dataSource.has(campaignStringPath)) { - try (InputStream campaignStringStream = dataSource.getResourceAsStream(campaignStringPath)) { - this.campaignStrings.readTXT(campaignStringStream, true); - } - catch (final IOException e) { - throw new RuntimeException(e); - } - } - else { - try (InputStream campaignStringStream = dataSource.getResourceAsStream("UI\\CampaignInfoClassic.txt")) { - this.campaignStrings.readTXT(campaignStringStream, true); - this.unifiedCampaignInfo = true; - } - catch (final IOException e) { - throw new RuntimeException(e); - } - } - this.profileManager = PlayerProfileManager.loadFromGdx(); + + this.musicSLK = new DataTable(StringBundle.EMPTY); + final String musicSLKPath = "UI\\SoundInfo\\Music.SLK"; + if (viewer.dataSource.has(musicSLKPath)) { + try (InputStream miscDataTxtStream = viewer.dataSource.getResourceAsStream(musicSLKPath)) { + this.musicSLK.readSLK(miscDataTxtStream); + } + catch (final IOException e) { + e.printStackTrace(); + } + } } public float getHeightRatioCorrection() { @@ -249,12 +254,32 @@ public class MenuUI { throw new IllegalStateException("Unable to load SmashFrameDef.toc", exc); } + this.campaignStrings = new DataTable(StringBundle.EMPTY); + final String campaignStringPath = this.rootFrame.trySkinField("CampaignFile"); + if (this.dataSource.has(campaignStringPath)) { + try (InputStream campaignStringStream = this.dataSource.getResourceAsStream(campaignStringPath)) { + this.campaignStrings.readTXT(campaignStringStream, true); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + else { + try (InputStream campaignStringStream = this.dataSource + .getResourceAsStream("UI\\CampaignInfoClassic.txt")) { + this.campaignStrings.readTXT(campaignStringStream, true); + this.unifiedCampaignInfo = true; + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } + // Create main menu this.mainMenuFrame = this.rootFrame.createFrame("MainMenuFrame", this.rootFrame, 0, 0); this.warcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); - this.rootFrame.setSpriteFrameModel(this.warcraftIIILogo, - this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); + this.rootFrame.setSpriteFrameModel(this.warcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo")); this.warcraftIIILogo.addSetPoint(new SetPoint(FramePoint.TOPLEFT, this.mainMenuFrame, FramePoint.TOPLEFT, GameUI.convertX(this.uiViewport, 0.13f), GameUI.convertY(this.uiViewport, -0.08f))); setMainMenuVisible(false); @@ -263,24 +288,29 @@ public class MenuUI { this.glueSpriteLayerTopRight = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashGlueSpriteLayerTopRight", this.rootFrame, "", 0); this.glueSpriteLayerTopRight.setSetAllPoints(true); - final String topRightModel = this.rootFrame - .getSkinField("GlueSpriteLayerTopRight_V" + WarsmashConstants.GAME_VERSION); + final String topRightModel = this.rootFrame.getSkinField("GlueSpriteLayerTopRight"); this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopRight, topRightModel); this.glueSpriteLayerTopRight.setSequence("MainMenu Birth"); this.glueSpriteLayerTopLeft = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashGlueSpriteLayerTopLeft", this.rootFrame, "", 0); this.glueSpriteLayerTopLeft.setSetAllPoints(true); - final String topLeftModel = this.rootFrame - .getSkinField("GlueSpriteLayerTopLeft_V" + WarsmashConstants.GAME_VERSION); + final String topLeftModel = this.rootFrame.getSkinField("GlueSpriteLayerTopLeft"); this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerTopLeft, topLeftModel); this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); + this.glueSpriteLayerCenter = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", + "SmashGlueSpriteLayerCenter", this.rootFrame, "", 0); + this.glueSpriteLayerCenter.setSetAllPoints(true); + final String centerModel = this.rootFrame.getSkinField("GlueSpriteLayerCenter"); + this.rootFrame.setSpriteFrameModel(this.glueSpriteLayerCenter, centerModel); + this.glueSpriteLayerCenter.setVisible(false); + this.cursorFrame = (SpriteFrame) this.rootFrame.createFrameByType("SPRITE", "SmashCursorFrame", this.rootFrame, "", 0); this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); this.cursorFrame.setSequence("Normal"); - this.cursorFrame.setZDepth(1.0f); + this.cursorFrame.setZDepth(1024.0f); if (WarsmashConstants.CATCH_CURSOR) { Gdx.input.setCursorCatched(true); } @@ -308,8 +338,6 @@ public class MenuUI { }); } - this.battleNetButton.setEnabled(false); - this.realmButton.setEnabled(false); this.localAreaNetworkButton.setEnabled(false); this.optionsButton.setEnabled(false); this.creditsButton.setEnabled(false); @@ -334,6 +362,22 @@ public class MenuUI { } }); + this.battleNetButton.setOnClick(new Runnable() { + @Override + public void run() { + MenuUI.this.glueSpriteLayerTopLeft.setSequence("MainMenu Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("MainMenu Death"); + setMainMenuVisible(false); + MenuUI.this.menuState = MenuState.GOING_TO_BATTLE_NET_LOGIN; + } + }); + this.realmButton.setOnClick(new Runnable() { + @Override + public void run() { + + } + }); + // Create single player this.singlePlayerMenu = this.rootFrame.createFrame("SinglePlayerMenu", this.rootFrame, 0, 0); this.singlePlayerMenu.setVisible(false); @@ -533,34 +577,36 @@ public class MenuUI { mapListBox.setSelectionListener(new ListBoxSelelectionListener() { @Override public void onSelectionChanged(final int newSelectedIndex, final String newSelectedItem) { - try { - final War3Map map = War3MapViewer.beginLoadingMap(MenuUI.this.dataSource, newSelectedItem); - final War3MapW3i mapInfo = map.readMapInformation(); - final WTS wtsFile = Warcraft3MapObjectData.loadWTS(map); - MenuUI.this.rootFrame.setMapStrings(wtsFile); - final War3MapConfig war3MapConfig = new War3MapConfig(WarsmashConstants.MAX_PLAYERS); - for (int i = 0; (i < WarsmashConstants.MAX_PLAYERS) && (i < mapInfo.getPlayers().size()); i++) { - final CBasePlayer player = war3MapConfig.getPlayer(i); - player.setName(MenuUI.this.rootFrame.getTrigStr(mapInfo.getPlayers().get(i).getName())); - } - Jass2.loadConfig(map, MenuUI.this.uiViewport, MenuUI.this.uiScene, MenuUI.this.rootFrame, - war3MapConfig, "Scripts\\common.j", "Scripts\\Blizzard.j", "war3map.j").config(); - for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { - final CBasePlayer player = war3MapConfig.getPlayer(i); - if (player.getController() == CMapControl.USER) { - player.setSlotState(CPlayerSlotState.PLAYING); - player.setName(MenuUI.this.profileManager.getCurrentProfile()); - break; + if (newSelectedItem != null) { + try { + final War3Map map = War3MapViewer.beginLoadingMap(MenuUI.this.dataSource, newSelectedItem); + final War3MapW3i mapInfo = map.readMapInformation(); + final WTS wtsFile = Warcraft3MapObjectData.loadWTS(map); + MenuUI.this.rootFrame.setMapStrings(wtsFile); + final War3MapConfig war3MapConfig = new War3MapConfig(WarsmashConstants.MAX_PLAYERS); + for (int i = 0; (i < WarsmashConstants.MAX_PLAYERS) && (i < mapInfo.getPlayers().size()); i++) { + final CBasePlayer player = war3MapConfig.getPlayer(i); + player.setName(MenuUI.this.rootFrame.getTrigStr(mapInfo.getPlayers().get(i).getName())); } + Jass2.loadConfig(map, MenuUI.this.uiViewport, MenuUI.this.uiScene, MenuUI.this.rootFrame, + war3MapConfig, "Scripts\\common.j", "Scripts\\Blizzard.j", "war3map.j").config(); + for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { + final CBasePlayer player = war3MapConfig.getPlayer(i); + if (player.getController() == CMapControl.USER) { + player.setSlotState(CPlayerSlotState.PLAYING); + player.setName(MenuUI.this.profileManager.getCurrentProfile()); + break; + } + } + MenuUI.this.skirmishMapInfoPane.setMap(MenuUI.this.rootFrame, MenuUI.this.uiViewport, map, + mapInfo, war3MapConfig); + teamSetupPane.setMap(map, MenuUI.this.rootFrame, MenuUI.this.uiViewport, war3MapConfig, + mapInfo.getPlayers().size()); + MenuUI.this.currentMapConfig = war3MapConfig; + } + catch (final IOException e) { + e.printStackTrace(); } - MenuUI.this.skirmishMapInfoPane.setMap(MenuUI.this.rootFrame, MenuUI.this.uiViewport, map, mapInfo, - war3MapConfig); - teamSetupPane.setMap(map, MenuUI.this.rootFrame, MenuUI.this.uiViewport, war3MapConfig, - mapInfo.getPlayers().size()); - MenuUI.this.currentMapConfig = war3MapConfig; - } - catch (final IOException e) { - e.printStackTrace(); } } }); @@ -616,8 +662,7 @@ public class MenuUI { this.campaignSelectFrame.setVisible(false); this.campaignWarcraftIIILogo = (SpriteFrame) this.rootFrame.getFrameByName("WarCraftIIILogo", 0); - this.rootFrame.setSpriteFrameModel(this.campaignWarcraftIIILogo, - this.rootFrame.getSkinField("MainMenuLogo_V" + WarsmashConstants.GAME_VERSION)); + this.rootFrame.setSpriteFrameModel(this.campaignWarcraftIIILogo, this.rootFrame.getSkinField("MainMenuLogo")); this.campaignWarcraftIIILogo.setVisible(false); this.campaignWarcraftIIILogo .addSetPoint(new SetPoint(FramePoint.TOPRIGHT, this.campaignMenu, FramePoint.TOPRIGHT, @@ -742,6 +787,69 @@ public class MenuUI { this.loadingMeleePanel = this.rootFrame.getFrameByName("LoadingMeleePanel", 0); this.loadingMeleePanel.setVisible(false); + this.battleNetUI = new BattleNetUI(this.rootFrame, this.uiViewport, new BattleNetUIActionListener() { + @Override + public void cancelLoginPrompt() { + MenuUI.this.battleNetUI.hide(); + MenuUI.this.battleNetUI.getDoors().setSequence(PrimaryTag.DEATH); + MenuUI.this.menuScreen.unAlternateModelBackToNormal(); + MenuUI.this.menuState = MenuState.LEAVING_BATTLE_NET; + } + + @Override + public void recoverPassword(final String text) { + + } + + @Override + public void logon(final String accountName, final String password) { + // TODO: connection + MenuUI.this.battleNetUI.loginAccepted(); + MenuUI.this.battleNetUI.getDoors().setSequence(PrimaryTag.DEATH); + MenuUI.this.menuState = MenuState.GOING_TO_BATTLE_NET_WELCOME; + } + + @Override + public void quitBattleNet() { + MenuUI.this.battleNetUI.hide(); + playCurrentBattleNetGlueSpriteDeath(); + MenuUI.this.glueSpriteLayerCenter.setSequence("Death"); + MenuUI.this.menuState = MenuState.LEAVING_BATTLE_NET_FROM_LOGGED_IN; + } + + public void playCurrentBattleNetGlueSpriteDeath() { + switch (MenuUI.this.menuState) { + case BATTLE_NET_CHAT_CHANNEL: + case GOING_TO_BATTLE_NET_CHAT_CHANNEL: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetChatRoom Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetChatRoom Death"); + break; + default: + case BATTLE_NET_WELCOME: + case GOING_TO_BATTLE_NET_WELCOME: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetWelcome Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetWelcome Death"); + break; + } + } + + @Override + public void openCustomGameMenu() { + MenuUI.this.battleNetUI.hideCurrentScreen(); + playCurrentBattleNetGlueSpriteDeath(); + MenuUI.this.glueSpriteLayerCenter.setSequence("Death"); + MenuUI.this.menuState = MenuState.GOING_TO_BATTLE_NET_CUSTOM_GAME_MENU; + } + + @Override + public void enterDefaultChat() { + MenuUI.this.battleNetUI.hideWelcomeScreen(); + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetWelcome Death"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetWelcome Death"); + MenuUI.this.menuState = MenuState.GOING_TO_BATTLE_NET_CHAT_CHANNEL; + } + }); + // position all this.rootFrame.positionBounds(this.rootFrame, this.uiViewport); @@ -749,24 +857,24 @@ public class MenuUI { loadSounds(); - final String glueLoopField = this.rootFrame.getSkinField("GlueScreenLoop_V" + WarsmashConstants.GAME_VERSION); + final String glueLoopField = this.rootFrame.getSkinField("GlueScreenLoop"); this.mainMenuGlueScreenLoop = this.uiSounds.getSound(glueLoopField); this.glueScreenLoop = this.mainMenuGlueScreenLoop; this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); } + public void show() { + playMusic(this.rootFrame.trySkinField("GlueMusic"), true, 0); + this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); + } + private void internalStartMap(final String mapFilename) { this.loadingFrame.setVisible(true); this.loadingBar.setVisible(true); this.loadingCustomPanel.setVisible(true); final DataSource codebase = WarsmashGdxMapScreen.parseDataSources(this.warsmashIni); final GameTurnManager turnManager; - if (MultiplayerHack.MULTIPLAYER_HACK_SERVER_ADDR != null) { - turnManager = GameTurnManager.PAUSED; - } - else { - turnManager = GameTurnManager.LOCAL; - } + turnManager = GameTurnManager.PAUSED; final War3MapViewer viewer = new War3MapViewer(codebase, this.screenManager, this.currentMapConfig, turnManager); @@ -930,8 +1038,29 @@ public class MenuUI { uiOrderListener = new WarsmashClientSendingOrderListener(warsmashClientWriter); } else { - uiOrderListener = new CPlayerUnitOrderExecutor(this.loadingMap.viewer.simulation, localPlayerIndex); + final CPlayerUnitOrderExecutor executor = new CPlayerUnitOrderExecutor( + this.loadingMap.viewer.simulation, localPlayerIndex); + final CPlayerUnitOrderListenerDelaying delayingListener = new CPlayerUnitOrderListenerDelaying( + executor); + uiOrderListener = delayingListener; warsmashClient = null; + final War3MapViewer mapViewer = this.loadingMap.viewer; + mapViewer.setGameTurnManager(new GameTurnManager() { + @Override + public void turnCompleted(final int gameTurnTick) { + delayingListener.publishDelayedActions(); + } + + @Override + public int getLatestCompletedTurn() { + return Integer.MAX_VALUE; + } + + @Override + public void framesSkipped(final float skippedCount) { + + } + }); } MenuUI.this.screenManager.setScreen(new WarsmashGdxMapScreen(this.loadingMap.viewer, this.screenManager, @@ -974,11 +1103,16 @@ public class MenuUI { this.cursorFrame.setSequence("Normal"); if (this.glueSpriteLayerTopRight.isSequenceEnded() && this.glueSpriteLayerTopLeft.isSequenceEnded() - && (!this.campaignFade.isVisible() || this.campaignFade.isSequenceEnded())) { + && (!this.campaignFade.isVisible() || this.campaignFade.isSequenceEnded()) + && (!this.battleNetUI.getDoors().isVisible() || this.battleNetUI.getDoors().isSequenceEnded())) { switch (this.menuState) { case GOING_TO_MAIN_MENU: this.glueSpriteLayerTopLeft.setSequence("MainMenu Birth"); this.glueSpriteLayerTopRight.setSequence("MainMenu Birth"); + if (this.battleNetUI.getDoors().isVisible()) { + this.battleNetUI.getDoors().setVisible(false); + this.battleNetUI.setVisible(false); + } this.menuState = MenuState.MAIN_MENU; break; case MAIN_MENU: @@ -986,6 +1120,65 @@ public class MenuUI { this.glueSpriteLayerTopLeft.setSequence("MainMenu Stand"); this.glueSpriteLayerTopRight.setSequence("MainMenu Stand"); break; + case GOING_TO_BATTLE_NET_LOGIN: + this.glueSpriteLayerTopLeft.setSequence("Death"); + this.glueSpriteLayerTopRight.setSequence("Death"); + MenuUI.this.battleNetUI.setVisible(true); + final SpriteFrame doors = MenuUI.this.battleNetUI.getDoors(); + doors.setVisible(true); + doors.setSequence(PrimaryTag.BIRTH); + this.menuState = MenuState.GOING_TO_BATTLE_NET_LOGIN_PART2; + break; + case GOING_TO_BATTLE_NET_LOGIN_PART2: + MenuUI.this.menuScreen.alternateModelToBattlenet(); + this.battleNetUI.showLoginPrompt(""); + this.menuState = MenuState.BATTLE_NET_LOGIN; + break; + case LEAVING_BATTLE_NET_FROM_LOGGED_IN: + MenuUI.this.menuScreen.unAlternateModelBackToNormal(); + this.glueSpriteLayerCenter.setVisible(false); + playMusic(this.rootFrame.trySkinField("GlueMusic"), true, 0); + // no break + case LEAVING_BATTLE_NET: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("Birth"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("Birth"); + this.menuState = MenuState.GOING_TO_MAIN_MENU; + break; + case GOING_TO_BATTLE_NET_WELCOME: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetWelcome Birth"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetWelcome Birth"); + this.glueSpriteLayerCenter.setVisible(true); + this.glueSpriteLayerCenter.setSequence("Birth"); + this.menuState = MenuState.BATTLE_NET_WELCOME; + playMusic(this.rootFrame.trySkinField("ChatMusic"), true, 0); + break; + case BATTLE_NET_WELCOME: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetWelcome Stand"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetWelcome Stand"); + this.battleNetUI.showWelcomeScreen(); + this.glueSpriteLayerCenter.setSequence("Stand"); + this.menuState = MenuState.BATTLE_NET_WELCOME; + break; + case GOING_TO_BATTLE_NET_CUSTOM_GAME_MENU: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetCustom Birth"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetCustom Birth"); + this.menuState = MenuState.BATTLE_NET_CUSTOM_GAME_MENU; + break; + case BATTLE_NET_CUSTOM_GAME_MENU: + this.battleNetUI.showCustomGameMenu(); + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetCustom Stand"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetCustom Stand"); + break; + case GOING_TO_BATTLE_NET_CHAT_CHANNEL: + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetChatRoom Birth"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetChatRoom Birth"); + this.menuState = MenuState.BATTLE_NET_CHAT_CHANNEL; + break; + case BATTLE_NET_CHAT_CHANNEL: + this.battleNetUI.showChatChannel(); + MenuUI.this.glueSpriteLayerTopLeft.setSequence("BattleNetChatRoom Stand"); + MenuUI.this.glueSpriteLayerTopRight.setSequence("BattleNetChatRoom Stand"); + break; case GOING_TO_SINGLE_PLAYER: this.glueSpriteLayerTopLeft.setSequence("SinglePlayer Birth"); this.glueSpriteLayerTopRight.setSequence("SinglePlayer Birth"); @@ -1003,8 +1196,7 @@ public class MenuUI { this.glueScreenLoop.stop(); this.glueScreenLoop = this.mainMenuGlueScreenLoop; this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); - this.menuScreen.setModel( - this.rootFrame.getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)); + this.menuScreen.setModel(this.rootFrame.getSkinField("GlueSpriteLayerBackground")); this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); this.menuState = MenuState.GOING_TO_SINGLE_PLAYER; break; @@ -1212,6 +1404,15 @@ public class MenuUI { private static enum MenuState { GOING_TO_MAIN_MENU, MAIN_MENU, + GOING_TO_BATTLE_NET_LOGIN, + GOING_TO_BATTLE_NET_LOGIN_PART2, + BATTLE_NET_LOGIN, + LEAVING_BATTLE_NET, + LEAVING_BATTLE_NET_FROM_LOGGED_IN, + GOING_TO_BATTLE_NET_CUSTOM_GAME_MENU, + BATTLE_NET_CUSTOM_GAME_MENU, + GOING_TO_BATTLE_NET_WELCOME, + BATTLE_NET_WELCOME, GOING_TO_SINGLE_PLAYER, LEAVING_CAMPAIGN, SINGLE_PLAYER, @@ -1227,11 +1428,14 @@ public class MenuUI { SINGLE_PLAYER_PROFILE, GOING_TO_LOADING_SCREEN, QUITTING, - RESTARTING; + RESTARTING, + GOING_TO_BATTLE_NET_CHAT_CHANNEL, + BATTLE_NET_CHAT_CHANNEL; } public void hide() { this.glueScreenLoop.stop(); + stopMusic(); } public void dispose() { @@ -1276,8 +1480,7 @@ public class MenuUI { this.glueScreenLoop.stop(); this.glueScreenLoop = this.mainMenuGlueScreenLoop; this.glueScreenLoop.play(this.uiScene.audioContext, 0f, 0f, 0f); - this.menuScreen.setModel( - this.rootFrame.getSkinField("GlueSpriteLayerBackground_V" + WarsmashConstants.GAME_VERSION)); + this.menuScreen.setModel(this.rootFrame.getSkinField("GlueSpriteLayerBackground")); this.rootFrame.setSpriteFrameModel(this.cursorFrame, this.rootFrame.getSkinField("Cursor")); break; case CAMPAIGN: @@ -1314,7 +1517,7 @@ public class MenuUI { private String getCurrentBackgroundModel() { final String background = this.currentCampaign.getBackground(); - final String versionedBackground = background + "_V" + WarsmashConstants.GAME_VERSION; + final String versionedBackground = background; if (this.rootFrame.hasSkinField(versionedBackground)) { return this.rootFrame.getSkinField(versionedBackground); } @@ -1334,4 +1537,48 @@ public class MenuUI { } } + + private void stopMusic() { + if (this.currentMusics != null) { + for (final Music music : this.currentMusics) { + music.stop(); + } + this.currentMusics = null; + } + } + + public Music playMusic(final String musicField, final boolean random, int index) { + if (WarsmashConstants.ENABLE_MUSIC) { + stopMusic(); + + final String[] semicolonMusics = musicField.split(";"); + final List musicPaths = new ArrayList<>(); + for (String musicPath : semicolonMusics) { + // dumb support for comma as well as semicolon, I wonder if we can + // clean this up, simplify? + if (this.musicSLK.get(musicPath) != null) { + musicPath = this.musicSLK.get(musicPath).getField("FileNames"); + } + final String[] moreSplitMusics = musicPath.split(","); + for (final String finalSplitPath : moreSplitMusics) { + musicPaths.add(finalSplitPath); + } + } + final String[] musics = musicPaths.toArray(new String[musicPaths.size()]); + + if (random) { + index = (int) (Math.random() * musics.length); + } + this.currentMusics = new Music[musics.length]; + for (int i = 0; i < musics.length; i++) { + final Music newMusic = Gdx.audio.newMusic(new DataSourceFileHandle(this.viewer.dataSource, musics[i])); + newMusic.setVolume(1.0f); + this.currentMusics[i] = newMusic; + } + this.currentMusicIndex = index; + this.currentMusicRandomizeIndex = random; + this.currentMusics[index].play(); + } + return null; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java index 576fa4a..4d84f92 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MultiSelectionIcon.java @@ -88,7 +88,7 @@ public class MultiSelectionIcon extends AbstractRenderableFrame implements Click } @Override - public void mouseDragged(GameUI rootFrame, Viewport uiViewport, float x, float y) { + public void mouseDragged(final GameUI rootFrame, final Viewport uiViewport, final float x, final float y) { } @@ -204,6 +204,11 @@ public class MultiSelectionIcon extends AbstractRenderableFrame implements Click return 0; } + @Override + public int getToolTipManaCost() { + return 0; + } + public void setLifeRatioRemaining(final float lifeRatioRemaining) { this.hpBarFrame.getBarFrame().setColor(Math.min(1.0f, 2.0f - (lifeRatioRemaining * 2)), Math.min(1.0f, lifeRatioRemaining * 2), 0, 1.0f); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java index 62358d5..1df7d3b 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/QueueIcon.java @@ -75,7 +75,7 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio } @Override - public void mouseDragged(GameUI rootFrame, Viewport uiViewport, float x, float y) { + public void mouseDragged(final GameUI rootFrame, final Viewport uiViewport, final float x, final float y) { } @@ -153,4 +153,9 @@ public class QueueIcon extends AbstractRenderableFrame implements ClickableActio public int getToolTipLumberCost() { return 0; } + + @Override + public int getToolTipManaCost() { + return 0; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java index 25f1110..41be4dc 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/ClickableActionFrame.java @@ -22,4 +22,6 @@ public interface ClickableActionFrame extends ClickableFrame { int getToolTipLumberCost(); int getToolTipFoodCost(); + + int getToolTipManaCost(); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java index beb10c7..ad0b1fe 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/CommandErrorListener.java @@ -7,7 +7,10 @@ public interface CommandErrorListener { void showNoFoodError(int playerIndex); + void showNoManaError(int playerIndex); + void showInventoryFullError(int playerIndex); void showUnableToFindCoupleTargetError(int playerIndex); + } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java index 0936bfd..2749574 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/command/SettableCommandErrorListener.java @@ -18,6 +18,11 @@ public class SettableCommandErrorListener implements CommandErrorListener { this.delegate.showNoFoodError(playerIndex); } + @Override + public void showNoManaError(final int playerIndex) { + this.delegate.showNoManaError(playerIndex); + } + @Override public void showInventoryFullError(final int playerIndex) { this.delegate.showInventoryFullError(playerIndex); diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUI.java new file mode 100644 index 0000000..1873757 --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUI.java @@ -0,0 +1,326 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +import java.awt.Desktop; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import com.badlogic.gdx.utils.viewport.Viewport; +import com.etheller.warsmash.parsers.fdf.GameUI; +import com.etheller.warsmash.parsers.fdf.frames.BackdropFrame; +import com.etheller.warsmash.parsers.fdf.frames.EditBoxFrame; +import com.etheller.warsmash.parsers.fdf.frames.GlueButtonFrame; +import com.etheller.warsmash.parsers.fdf.frames.SimpleFrame; +import com.etheller.warsmash.parsers.fdf.frames.SpriteFrame; +import com.etheller.warsmash.parsers.fdf.frames.StringFrame; +import com.etheller.warsmash.parsers.fdf.frames.UIFrame; + +public class BattleNetUI { + private final GameUI rootFrame; + private final Viewport uiViewport; + + private final UIFrame battleNetMainFrame; + private final UIFrame battleNetChangePasswordPanel; + private final UIFrame battleNetChangeEmailPanel; + private final UIFrame battleNetPasswordRecoveryPanel; + private final UIFrame battleNetEmailBindPanel; + private final UIFrame battleNetTOSPanel; + private final UIFrame battleNetNewAccountPanel; + private final UIFrame battleNetCancelBackdrop; + private final GlueButtonFrame cancelButton; + private final UIFrame battleNetOKBackdrop; + private final UIFrame battleNetLoginPanel; + private final SpriteFrame battleNetDoors; + + private final EditBoxFrame accountNameEditBox; + private final EditBoxFrame passwordEditBox; + private final GlueButtonFrame passwordRecoveryButton; + + private final BattleNetUIActionListener actionListener; + private final StringFrame selectedRealmValue; + private final GlueButtonFrame changeEmailButton; + private final GlueButtonFrame changePasswordButton; + private final GlueButtonFrame newAccountButton; + private final GlueButtonFrame tosButton; + private final Runnable exitLoginRunnable; + private final GlueButtonFrame logonButton; + private final UIFrame battleNetChatPanel; + private final UIFrame chatPanel; + private final UIFrame channelPanel; + private final GlueButtonFrame quitBattleNetButton; + private final BackdropFrame adFrame; + private final BackdropFrame logoFrame; + private final List battleNetChatTopButtons = new ArrayList<>(); + private final GlueButtonFrame standardGameButton; + private final GlueButtonFrame quickStandardGameButton; + private final GlueButtonFrame standardTeamGameButton; + private final GlueButtonFrame customGameButton; + private final GlueButtonFrame tournamentButton; + private final GlueButtonFrame ladderButton; + private final GlueButtonFrame profileButton; + // welcome panel: + private final UIFrame welcomePanel; + private final StringFrame welcomeNewItemCount; + private final SimpleFrame welcomeNewsBoxContainer; + private final StringFrame welcomeMOTDText; + private final UIFrame welcomeUpcomingTournamentPanel; + private final GlueButtonFrame welcomeEnterChatButton; + private final SimpleFrame welcomeQuitBattleNetButtonContainer; + private final SimpleFrame chatQuitBattleNetButtonContainer; + + public BattleNetUI(final GameUI rootFrame, final Viewport uiViewport, + final BattleNetUIActionListener actionListener) { + this.rootFrame = rootFrame; + this.uiViewport = uiViewport; + this.actionListener = actionListener; + // Create BattleNet frames + this.battleNetMainFrame = rootFrame.createFrame("BattleNetMainFrame", rootFrame, 0, 0); + this.battleNetMainFrame.setVisible(false); + this.battleNetDoors = (SpriteFrame) rootFrame.getFrameByName("BattleNetMainBackground", 0); + this.battleNetDoors.setVisible(false); + this.battleNetChangePasswordPanel = rootFrame.getFrameByName("ChangePasswordPanel", 0); + this.battleNetChangePasswordPanel.setVisible(false); + this.battleNetChangeEmailPanel = rootFrame.getFrameByName("ChangeEmailPanel", 0); + this.battleNetChangeEmailPanel.setVisible(false); + this.battleNetPasswordRecoveryPanel = rootFrame.getFrameByName("PasswordRecoveryPanel", 0); + this.battleNetPasswordRecoveryPanel.setVisible(false); + this.battleNetEmailBindPanel = rootFrame.getFrameByName("EmailBindPanel", 0); + this.battleNetEmailBindPanel.setVisible(false); + + // ******************************************* + // * + // * Terms Of Service Panel + // * + // ****** + + this.battleNetTOSPanel = rootFrame.getFrameByName("TOSPanel", 0); + this.battleNetTOSPanel.setVisible(false); + + final UIFrame tosTextArea = rootFrame.getFrameByName("TOSTextArea", 0); + + this.battleNetNewAccountPanel = rootFrame.getFrameByName("NewAccountPanel", 0); + this.battleNetNewAccountPanel.setVisible(false); + this.battleNetCancelBackdrop = rootFrame.getFrameByName("CancelBackdrop", 0); + this.battleNetCancelBackdrop.setVisible(false); + this.cancelButton = (GlueButtonFrame) rootFrame.getFrameByName("CancelButton", 0); + this.battleNetOKBackdrop = rootFrame.getFrameByName("OKBackdrop", 0); + this.battleNetOKBackdrop.setVisible(false); + + // ******************************************* + // * + // * Main Login Panel + // * + // ****** + + this.battleNetLoginPanel = rootFrame.getFrameByName("LoginPanel", 0); + this.battleNetLoginPanel.setVisible(false); + + this.accountNameEditBox = (EditBoxFrame) rootFrame.getFrameByName("AccountName", 0); + this.passwordEditBox = (EditBoxFrame) rootFrame.getFrameByName("Password", 0); + this.passwordRecoveryButton = (GlueButtonFrame) rootFrame.getFrameByName("PasswordRecoveryButton", 0); + this.passwordRecoveryButton.setOnClick(new Runnable() { + @Override + public void run() { + actionListener.recoverPassword(BattleNetUI.this.accountNameEditBox.getText()); + } + }); + this.selectedRealmValue = (StringFrame) rootFrame.getFrameByName("SelectedRealmValue", 0); + this.changeEmailButton = (GlueButtonFrame) rootFrame.getFrameByName("ChangeEmailButton", 0); + this.changeEmailButton.setEnabled(false); + this.changePasswordButton = (GlueButtonFrame) rootFrame.getFrameByName("ChangePasswordButton", 0); + this.newAccountButton = (GlueButtonFrame) rootFrame.getFrameByName("NewAccountButton", 0); + this.tosButton = (GlueButtonFrame) rootFrame.getFrameByName("TOSButton", 0); + this.tosButton.setOnClick(new Runnable() { + @Override + public void run() { + boolean success = false; + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + try { + Desktop.getDesktop().browse(new URI("https://www.youtube.com/watch?v=dQw4w9WgXcQ")); + success = true; + } + catch (final Exception e) { + e.printStackTrace(); + } + } + if (!success) { + BattleNetUI.this.battleNetLoginPanel.setVisible(false); + BattleNetUI.this.battleNetCancelBackdrop.setVisible(false); + BattleNetUI.this.battleNetTOSPanel.setVisible(true); + } + } + }); + + this.exitLoginRunnable = new Runnable() { + @Override + public void run() { + BattleNetUI.this.actionListener.cancelLoginPrompt(); + } + }; + + this.logonButton = (GlueButtonFrame) rootFrame.getFrameByName("LogonButton", 0); + this.logonButton.setOnClick(new Runnable() { + @Override + public void run() { + actionListener.logon(BattleNetUI.this.accountNameEditBox.getText(), + BattleNetUI.this.passwordEditBox.getText()); + } + }); + + this.battleNetChatPanel = rootFrame.createFrame("BattleNetChatPanel", rootFrame, 0, 0); + this.battleNetChatPanel.setVisible(false); + + // ******************************** + // * The chat panel + // ******************************** + this.adFrame = (BackdropFrame) rootFrame.getFrameByName("AdFrame", 0); + this.adFrame.setVisible(false); + this.logoFrame = (BackdropFrame) rootFrame.getFrameByName("LogoFrame", 0); + this.logoFrame.setVisible(false); + this.standardGameButton = (GlueButtonFrame) rootFrame.getFrameByName("StandardGameButton", 0); + this.standardGameButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.standardGameButton); + this.quickStandardGameButton = (GlueButtonFrame) rootFrame.getFrameByName("QuickStandardGameButton", 0); + this.quickStandardGameButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.quickStandardGameButton); + this.standardTeamGameButton = (GlueButtonFrame) rootFrame.getFrameByName("StandardTeamGameButton", 0); + this.standardTeamGameButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.standardTeamGameButton); + this.customGameButton = (GlueButtonFrame) rootFrame.getFrameByName("CustomGameButton", 0); + this.customGameButton.setOnClick(new Runnable() { + @Override + public void run() { + actionListener.openCustomGameMenu(); + } + }); + this.battleNetChatTopButtons.add(this.customGameButton); + this.tournamentButton = (GlueButtonFrame) rootFrame.getFrameByName("TournamentButton", 0); + this.tournamentButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.tournamentButton); + this.ladderButton = (GlueButtonFrame) rootFrame.getFrameByName("LadderButton", 0); + this.ladderButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.ladderButton); + this.profileButton = (GlueButtonFrame) rootFrame.getFrameByName("ProfileButton", 0); + this.profileButton.setEnabled(false); + this.battleNetChatTopButtons.add(this.profileButton); + this.chatPanel = rootFrame.getFrameByName("ChatPanel", 0); + this.chatPanel.setVisible(false); + this.chatQuitBattleNetButtonContainer = (SimpleFrame) rootFrame + .getFrameByName("ChatQuitBattleNetButtonContainer", 0); + + // ******************************** + // * The channel panel + // ******************************** + this.channelPanel = rootFrame.getFrameByName("ChannelPanel", 0); + this.channelPanel.setVisible(false); + + // ******************************** + // * The welcome panel + // ******************************** + this.welcomePanel = rootFrame.getFrameByName("WelcomePanel", 0); + this.welcomePanel.setVisible(false); + this.welcomeNewItemCount = (StringFrame) rootFrame.getFrameByName("WelcomeNewItemCount", 0); + rootFrame.setText(this.welcomeNewItemCount, "(0)"); + this.welcomeNewsBoxContainer = (SimpleFrame) rootFrame.getFrameByName("NewsBoxContainer", 0); + this.welcomeMOTDText = (StringFrame) rootFrame.getFrameByName("WelcomeMOTDText", 0); + rootFrame.setText(this.welcomeMOTDText, + "This MOTD is set from source code and is not an externalized string. |cffdd00ffWarsmash|r engine is producing this message locally.|n|n |cff00ff00TODO:|r Modify the |cffdd00ffWarsmash|r engine sourcecode to download a message from the server to put here that admins can customize!"); + this.welcomeUpcomingTournamentPanel = rootFrame.getFrameByName("UpcomingTournamentPanel", 0); + this.welcomeUpcomingTournamentPanel.setVisible(false); + this.welcomeEnterChatButton = (GlueButtonFrame) rootFrame.getFrameByName("EnterChatButton", 0); + this.welcomeEnterChatButton.setOnClick(new Runnable() { + @Override + public void run() { + actionListener.enterDefaultChat(); + } + }); + this.welcomeQuitBattleNetButtonContainer = (SimpleFrame) rootFrame + .getFrameByName("WelcomeQuitBattleNetButtonContainer", 0); + + // ******************************** + // * The quit button + // ******************************** + this.quitBattleNetButton = (GlueButtonFrame) rootFrame.getFrameByName("QuitBattleNetButton", 0); + this.quitBattleNetButton.setOnClick(new Runnable() { + @Override + public void run() { + actionListener.quitBattleNet(); + } + }); + } + + public void setTopButtonsVisible(final boolean flag) { + for (final GlueButtonFrame frame : this.battleNetChatTopButtons) { + frame.setVisible(flag); + } + } + + public SpriteFrame getDoors() { + return this.battleNetDoors; + } + + public void setVisible(final boolean b) { + this.battleNetMainFrame.setVisible(b); + } + + public void showLoginPrompt(final String selectedRealm) { + this.rootFrame.setText(this.selectedRealmValue, selectedRealm); + this.battleNetLoginPanel.setVisible(true); + this.battleNetCancelBackdrop.setVisible(true); + this.cancelButton.setOnClick(this.exitLoginRunnable); + } + + public void hide() { + this.battleNetLoginPanel.setVisible(false); + this.battleNetCancelBackdrop.setVisible(false); + this.battleNetChatPanel.setVisible(false); + hideCurrentScreen(); + } + + public void loginAccepted() { + this.battleNetLoginPanel.setVisible(false); + this.battleNetCancelBackdrop.setVisible(false); + } + + public void showWelcomeScreen() { + setTopButtonsVisible(true); + this.battleNetChatPanel.setVisible(true); + this.welcomePanel.setVisible(true); + this.quitBattleNetButton.setVisible(true); + this.quitBattleNetButton.setParent(this.welcomeQuitBattleNetButtonContainer); + this.quitBattleNetButton.setWidth(0); // TODO set width/height 0 probably shouldnt be necessary + this.quitBattleNetButton.setHeight(0); + this.quitBattleNetButton.clearFramePointAssignments(); + this.quitBattleNetButton.setSetAllPoints(true); + this.quitBattleNetButton.positionBounds(this.rootFrame, this.uiViewport); + } + + public void showCustomGameMenu() { + + } + + public void hideWelcomeScreen() { + this.welcomePanel.setVisible(false); + this.quitBattleNetButton.setVisible(false); + } + + public void hideCurrentScreen() { + this.welcomePanel.setVisible(false); + this.chatPanel.setVisible(false); + this.welcomePanel.setVisible(false); + this.quitBattleNetButton.setVisible(false); + setTopButtonsVisible(false); + } + + public void showChatChannel() { + setTopButtonsVisible(true); + this.chatPanel.setVisible(true); + this.quitBattleNetButton.setVisible(true); + this.quitBattleNetButton.setParent(this.chatQuitBattleNetButtonContainer); + this.quitBattleNetButton.setWidth(0); // TODO set width/height 0 probably shouldnt be necessary + this.quitBattleNetButton.setHeight(0); + this.quitBattleNetButton.clearFramePointAssignments(); + this.quitBattleNetButton.setSetAllPoints(true); + this.quitBattleNetButton.positionBounds(this.rootFrame, this.uiViewport); + } +} diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUIActionListener.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUIActionListener.java new file mode 100644 index 0000000..672933e --- /dev/null +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/menu/BattleNetUIActionListener.java @@ -0,0 +1,15 @@ +package com.etheller.warsmash.viewer5.handlers.w3x.ui.menu; + +public interface BattleNetUIActionListener { + void cancelLoginPrompt(); + + void recoverPassword(String text); + + void logon(String accountName, String password); + + void quitBattleNet(); + + void openCustomGameMenu(); + + void enterDefaultChat(); +} diff --git a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java index 99fef40..0c05c80 100644 --- a/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java +++ b/fdfparser/src/com/etheller/warsmash/parsers/fdf/datamodel/FontFlags.java @@ -1,5 +1,18 @@ package com.etheller.warsmash.parsers.fdf.datamodel; +import java.util.EnumSet; + public enum FontFlags { - FIXEDSIZE; + FIXEDSIZE, + PASSWORDFIELD; + + public static EnumSet parseFontFlags(final String cornerFlags) { + final EnumSet set = EnumSet.noneOf(FontFlags.class); + for (final String flag : cornerFlags.split("\\|")) { + if (!"".equals(flag)) { + set.add(FontFlags.valueOf(flag)); + } + } + return set; + } } diff --git a/server/build.gradle b/server/build.gradle new file mode 100644 index 0000000..78a1a9e --- /dev/null +++ b/server/build.gradle @@ -0,0 +1,42 @@ +apply plugin: "java" + +sourceCompatibility = 1.7 +sourceSets.main.java.srcDirs = [ "src/" ] +sourceSets.main.resources.srcDirs = [ "src/" ] + +project.ext.mainClassName = "com.etheller.warsmash.bigserver.GamingNetworkServerMain" + +task run(dependsOn: classes, type: JavaExec) { + main = project.mainClassName + classpath = sourceSets.main.runtimeClasspath + standardInput = System.in + ignoreExitValue = true +} + +task dist(type: Jar) { + from files(sourceSets.main.output.classesDir) + from files(sourceSets.main.output.resourcesDir) + from {configurations.compile.collect {zipTree(it)}} + + manifest { + attributes 'Main-Class': project.mainClassName + } +} + +dist.dependsOn classes + +eclipse { + project { + name = appName + "-server" + } +} + +task afterEclipseImport(description: "Post processing after project generation", group: "IDE") { + doLast { + def classpath = new XmlParser().parse(file(".classpath")) + def writer = new FileWriter(file(".classpath")) + def printer = new XmlNodePrinter(new PrintWriter(writer)) + printer.setPreserveWhitespace(true) + printer.print(classpath) + } +} diff --git a/settings.gradle b/settings.gradle index 5d318b0..5e78246 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include 'desktop', 'core', 'fdfparser', 'jassparser' \ No newline at end of file +include 'desktop', 'core', 'server', 'shared', 'fdfparser', 'jassparser' \ No newline at end of file diff --git a/shared/build.gradle b/shared/build.gradle new file mode 100644 index 0000000..f11f85d --- /dev/null +++ b/shared/build.gradle @@ -0,0 +1,11 @@ +apply plugin: "java" + +sourceCompatibility = 1.7 +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +sourceSets.main.java.srcDirs = [ "src/" ] + + +eclipse.project { + name = appName + "-shared" +} diff --git a/shared/src/net/warsmash/networking/tcp/TCPTestClient.java b/shared/src/net/warsmash/networking/tcp/TCPTestClient.java new file mode 100644 index 0000000..ab736ea --- /dev/null +++ b/shared/src/net/warsmash/networking/tcp/TCPTestClient.java @@ -0,0 +1,52 @@ +package net.warsmash.networking.tcp; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import net.warsmash.nio.channels.SelectableChannelOpener; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; + +public class TCPTestClient { + private static final int PORT = 8989; + + public static void main(final String[] args) { + final SelectableChannelOpener selectableChannelOpener = new SelectableChannelOpener(); + + final ByteBuffer writeBuffer = ByteBuffer.allocate(8 * 1024 * 1024).order(ByteOrder.BIG_ENDIAN); + final WritableOutput clientChannel = selectableChannelOpener + .openTCPClientChannel(new InetSocketAddress("localhost", PORT), new TCPClientParser() { + @Override + public void parse(final ByteBuffer data) { + System.out.println("Got " + data.remaining() + " bytes from server!"); + if (data.hasRemaining()) { + System.out.print("["); + System.out.print(data.get()); + while (data.hasRemaining()) { + System.out.print(", "); + System.out.print(data.get()); + } + System.out.println("]"); + } + } + + @Override + public void disconnected() { + System.out.println("TCP disconnected!"); + } + }, ExceptionListener.THROW_RUNTIME, 8 * 1024 * 1024, ByteOrder.BIG_ENDIAN); + + writeBuffer.clear(); + for (int i = 0; i < 4; i++) { + writeBuffer.put((byte) ((i * 2) + 1)); + } + writeBuffer.flip(); + clientChannel.write(writeBuffer); + + while (true) { + selectableChannelOpener.select(0); + } + } +} diff --git a/shared/src/net/warsmash/networking/tcp/TCPTestServer.java b/shared/src/net/warsmash/networking/tcp/TCPTestServer.java new file mode 100644 index 0000000..16abf2c --- /dev/null +++ b/shared/src/net/warsmash/networking/tcp/TCPTestServer.java @@ -0,0 +1,51 @@ +package net.warsmash.networking.tcp; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import net.warsmash.nio.channels.SelectableChannelOpener; +import net.warsmash.nio.channels.SocketChannelCallback; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; + +public class TCPTestServer { + private static final int PORT = 8989; + + public static void main(final String[] args) { + final SelectableChannelOpener selectableChannelOpener = new SelectableChannelOpener(); + selectableChannelOpener.openTCPServerChannel(PORT, new SocketChannelCallback() { + @Override + public TCPClientParser onConnect(final WritableOutput writableOpenedChannel, + final SocketAddress remoteAddress) { + System.out.println("Received connection from " + remoteAddress); + return new TCPClientParser() { + @Override + public void parse(final ByteBuffer data) { + System.out.println("Got " + data.remaining() + " bytes from " + remoteAddress); + if (data.hasRemaining()) { + System.out.print("["); + System.out.print(data.get()); + while (data.hasRemaining()) { + System.out.print(", "); + System.out.print(data.get()); + } + System.out.println("]"); + } + writableOpenedChannel.write(ByteBuffer.wrap("reply".getBytes())); + } + + @Override + public void disconnected() { + System.out.println("Disconnected from " + remoteAddress); + } + }; + } + }, ExceptionListener.THROW_RUNTIME, 8 * 1024 * 1024, ByteOrder.BIG_ENDIAN); + + while (true) { + selectableChannelOpener.select(0); + } + } +} diff --git a/shared/src/net/warsmash/networking/tcp/TestChatClient.java b/shared/src/net/warsmash/networking/tcp/TestChatClient.java new file mode 100644 index 0000000..fb3fb8a --- /dev/null +++ b/shared/src/net/warsmash/networking/tcp/TestChatClient.java @@ -0,0 +1,59 @@ +package net.warsmash.networking.tcp; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import net.warsmash.nio.channels.SelectableChannelOpener; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; + +public class TestChatClient { + private static final int PORT = 8989; + + public static void main(final String[] args) { + final SelectableChannelOpener selectableChannelOpener = new SelectableChannelOpener(); + + final ByteBuffer writeBuffer = ByteBuffer.allocate(8 * 1024 * 1024).order(ByteOrder.BIG_ENDIAN); + final WritableOutput clientChannel = selectableChannelOpener + .openTCPClientChannel(new InetSocketAddress("localhost", PORT), new TCPClientParser() { + @Override + public void parse(final ByteBuffer data) { + final byte[] bs = new byte[data.remaining()]; + data.get(bs); + final String message = new String(bs); + System.out.println(message); + } + + @Override + public void disconnected() { + System.out.println("TCP disconnected!"); + } + }, ExceptionListener.THROW_RUNTIME, 8 * 1024 * 1024, ByteOrder.BIG_ENDIAN); + + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + selectableChannelOpener.select(0); + } + } + }).start(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) { + String line; + while ((line = reader.readLine()) != null) { + writeBuffer.clear(); + writeBuffer.put(line.getBytes()); + writeBuffer.flip(); + clientChannel.write(writeBuffer); + } + } + catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/shared/src/net/warsmash/networking/tcp/TestChatServer.java b/shared/src/net/warsmash/networking/tcp/TestChatServer.java new file mode 100644 index 0000000..fb9cfbb --- /dev/null +++ b/shared/src/net/warsmash/networking/tcp/TestChatServer.java @@ -0,0 +1,55 @@ +package net.warsmash.networking.tcp; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashSet; +import java.util.Set; + +import net.warsmash.nio.channels.SelectableChannelOpener; +import net.warsmash.nio.channels.SocketChannelCallback; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; + +public class TestChatServer { + private static final int PORT = 8989; + + public static void main(final String[] args) { + final SelectableChannelOpener selectableChannelOpener = new SelectableChannelOpener(); + final Set outputs = new HashSet<>(); + selectableChannelOpener.openTCPServerChannel(PORT, new SocketChannelCallback() { + @Override + public TCPClientParser onConnect(final WritableOutput writableOpenedChannel, + final SocketAddress remoteAddress) { + System.out.println("Received connection from " + remoteAddress); + outputs.add(writableOpenedChannel); + return new TCPClientParser() { + @Override + public void parse(final ByteBuffer data) { + final byte[] bs = new byte[data.remaining()]; + data.get(bs); + final String message = new String(bs); + for (final WritableOutput output : outputs) { + output.write(ByteBuffer.wrap((remoteAddress.toString() + ":" + message).getBytes())); + } + } + + @Override + public void disconnected() { + outputs.remove(writableOpenedChannel); + for (final WritableOutput output : outputs) { + output.write( + ByteBuffer.wrap((remoteAddress.toString() + " has left the chat.").getBytes())); + } + System.out.println("Disconnected from " + remoteAddress); + } + }; + } + }, ExceptionListener.THROW_RUNTIME, 8 * 1024 * 1024, ByteOrder.BIG_ENDIAN); + + while (true) { + selectableChannelOpener.select(0); + } + } +} diff --git a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpClient.java b/shared/src/net/warsmash/networking/udp/OrderedUdpClient.java similarity index 94% rename from core/src/com/etheller/warsmash/networking/udp/OrderedUdpClient.java rename to shared/src/net/warsmash/networking/udp/OrderedUdpClient.java index 8e7329e..37d11c4 100644 --- a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpClient.java +++ b/shared/src/net/warsmash/networking/udp/OrderedUdpClient.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.io.IOException; import java.net.InetAddress; diff --git a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpClientListener.java b/shared/src/net/warsmash/networking/udp/OrderedUdpClientListener.java similarity index 68% rename from core/src/com/etheller/warsmash/networking/udp/OrderedUdpClientListener.java rename to shared/src/net/warsmash/networking/udp/OrderedUdpClientListener.java index cc4d0ee..ea0bfd5 100644 --- a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpClientListener.java +++ b/shared/src/net/warsmash/networking/udp/OrderedUdpClientListener.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; public interface OrderedUdpClientListener extends UdpClientListener { void cantReplay(int seqNo); diff --git a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpCommuncation.java b/shared/src/net/warsmash/networking/udp/OrderedUdpCommuncation.java similarity index 98% rename from core/src/com/etheller/warsmash/networking/udp/OrderedUdpCommuncation.java rename to shared/src/net/warsmash/networking/udp/OrderedUdpCommuncation.java index c2c7185..1f8ece3 100644 --- a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpCommuncation.java +++ b/shared/src/net/warsmash/networking/udp/OrderedUdpCommuncation.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpServer.java b/shared/src/net/warsmash/networking/udp/OrderedUdpServer.java similarity index 98% rename from core/src/com/etheller/warsmash/networking/udp/OrderedUdpServer.java rename to shared/src/net/warsmash/networking/udp/OrderedUdpServer.java index e973cc2..2a2f90a 100644 --- a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpServer.java +++ b/shared/src/net/warsmash/networking/udp/OrderedUdpServer.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.io.IOException; import java.net.SocketAddress; diff --git a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpServerListener.java b/shared/src/net/warsmash/networking/udp/OrderedUdpServerListener.java similarity index 77% rename from core/src/com/etheller/warsmash/networking/udp/OrderedUdpServerListener.java rename to shared/src/net/warsmash/networking/udp/OrderedUdpServerListener.java index 64b5df9..ff6dc8c 100644 --- a/core/src/com/etheller/warsmash/networking/udp/OrderedUdpServerListener.java +++ b/shared/src/net/warsmash/networking/udp/OrderedUdpServerListener.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.net.SocketAddress; diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpClient.java b/shared/src/net/warsmash/networking/udp/UdpClient.java similarity index 53% rename from core/src/com/etheller/warsmash/networking/udp/UdpClient.java rename to shared/src/net/warsmash/networking/udp/UdpClient.java index 99ae5d0..07f1836 100644 --- a/core/src/com/etheller/warsmash/networking/udp/UdpClient.java +++ b/shared/src/net/warsmash/networking/udp/UdpClient.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.io.IOException; import java.net.InetAddress; @@ -8,8 +8,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.DatagramChannel; -import com.etheller.warsmash.util.WarsmashConstants; - public class UdpClient implements Runnable { private final DatagramChannel channel; private final ByteBuffer readBuffer; @@ -50,44 +48,4 @@ public class UdpClient implements Runnable { } } - static UdpClient warsmashUdpClient; - - public static void main(final String[] args) { - try { - warsmashUdpClient = new UdpClient(InetAddress.getLocalHost(), WarsmashConstants.PORT_NUMBER, - new UdpClientListener() { - @Override - public void parse(final ByteBuffer buffer) { - System.out.println("got " + buffer.remaining() + " bytes, pos=" + buffer.position() - + ", lim=" + buffer.limit()); - System.out.println("got from server: " + buffer.getInt()); - } - }); - new Thread(warsmashUdpClient).start(); - - final ByteBuffer sendBuffer = ByteBuffer.allocate(1024); - for (int i = 0; i < 10; i++) { - sendBuffer.clear(); - sendBuffer.put((byte) (1 + i)); - sendBuffer.put((byte) 3); - sendBuffer.put((byte) 5); - sendBuffer.put((byte) 7); - sendBuffer.flip(); - warsmashUdpClient.send(sendBuffer); - try { - Thread.sleep(1000); - } - catch (final InterruptedException e) { - e.printStackTrace(); - } - } - } - catch (final UnknownHostException e) { - e.printStackTrace(); - } - catch (final IOException e) { - e.printStackTrace(); - } - } - } diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpClientListener.java b/shared/src/net/warsmash/networking/udp/UdpClientListener.java similarity index 68% rename from core/src/com/etheller/warsmash/networking/udp/UdpClientListener.java rename to shared/src/net/warsmash/networking/udp/UdpClientListener.java index 8ce037c..357764d 100644 --- a/core/src/com/etheller/warsmash/networking/udp/UdpClientListener.java +++ b/shared/src/net/warsmash/networking/udp/UdpClientListener.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.nio.ByteBuffer; diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpServer.java b/shared/src/net/warsmash/networking/udp/UdpServer.java similarity index 68% rename from core/src/com/etheller/warsmash/networking/udp/UdpServer.java rename to shared/src/net/warsmash/networking/udp/UdpServer.java index 2d62c14..b02c122 100644 --- a/core/src/com/etheller/warsmash/networking/udp/UdpServer.java +++ b/shared/src/net/warsmash/networking/udp/UdpServer.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.io.IOException; import java.net.InetSocketAddress; @@ -11,8 +11,6 @@ import java.nio.channels.Selector; import java.util.Iterator; import java.util.Set; -import com.etheller.warsmash.util.WarsmashConstants; - public class UdpServer implements Runnable { private final Selector selector; @@ -20,6 +18,7 @@ public class UdpServer implements Runnable { private final SelectionKey key; private final ByteBuffer readBuffer; private final UdpServerListener serverListener; + private final DatagramChannel channel; public UdpServer(final int portNumber, final UdpServerListener serverListener) throws IOException { this.serverListener = serverListener; @@ -72,37 +71,4 @@ public class UdpServer implements Runnable { this.running = running; } - static UdpServer warsmashGameServer; - private final DatagramChannel channel; - - public static void main(final String[] args) { - try { - warsmashGameServer = new UdpServer(WarsmashConstants.PORT_NUMBER, new UdpServerListener() { - int n = 0; - ByteBuffer sendBuffer = ByteBuffer.allocate(1024); - - @Override - public void parse(final SocketAddress sourceAddress, final ByteBuffer buffer) { - System.out.println("Got packet from: " + sourceAddress); - while (buffer.hasRemaining()) { - System.out.println("Received: " + buffer.get()); - } - try { - this.sendBuffer.clear(); - this.sendBuffer.putInt(this.n++); - this.sendBuffer.flip(); - warsmashGameServer.send(sourceAddress, this.sendBuffer); - } - catch (final IOException e) { - e.printStackTrace(); - } - } - }); - new Thread(warsmashGameServer).start(); - } - catch (final IOException e) { - e.printStackTrace(); - } - } - } diff --git a/core/src/com/etheller/warsmash/networking/udp/UdpServerListener.java b/shared/src/net/warsmash/networking/udp/UdpServerListener.java similarity index 77% rename from core/src/com/etheller/warsmash/networking/udp/UdpServerListener.java rename to shared/src/net/warsmash/networking/udp/UdpServerListener.java index 471ae7e..9a661a6 100644 --- a/core/src/com/etheller/warsmash/networking/udp/UdpServerListener.java +++ b/shared/src/net/warsmash/networking/udp/UdpServerListener.java @@ -1,4 +1,4 @@ -package com.etheller.warsmash.networking.udp; +package net.warsmash.networking.udp; import java.net.SocketAddress; import java.nio.ByteBuffer; diff --git a/shared/src/net/warsmash/nio/channels/ByteParser.java b/shared/src/net/warsmash/nio/channels/ByteParser.java new file mode 100644 index 0000000..50f8b14 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/ByteParser.java @@ -0,0 +1,7 @@ +package net.warsmash.nio.channels; + +import java.nio.ByteBuffer; + +public interface ByteParser { + void parse(ByteBuffer data); +} diff --git a/shared/src/net/warsmash/nio/channels/ChannelListener.java b/shared/src/net/warsmash/nio/channels/ChannelListener.java new file mode 100644 index 0000000..2e015c7 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/ChannelListener.java @@ -0,0 +1,7 @@ +package net.warsmash.nio.channels; + +public interface ChannelListener { + void channelOpened(); + + void channelClosed(); +} diff --git a/shared/src/net/warsmash/nio/channels/ChannelOpener.java b/shared/src/net/warsmash/nio/channels/ChannelOpener.java new file mode 100644 index 0000000..c3ca060 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/ChannelOpener.java @@ -0,0 +1,19 @@ +package net.warsmash.nio.channels; + +import java.net.SocketAddress; +import java.nio.ByteOrder; + +import net.warsmash.networking.udp.UdpServerListener; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.util.ExceptionListener; + +public interface ChannelOpener { + OpenedChannel openUDPServerChannel(int port, UdpServerListener listener, ExceptionListener exceptionListener, + int bufferSize, ByteOrder byteOrder); + + OpenedChannel openTCPServerChannel(int port, SocketChannelCallback callback, + final ExceptionListener exceptionListener, final int bufferSize, ByteOrder byteOrder); + + WritableOutput openTCPClientChannel(SocketAddress socketAddress, TCPClientParser tcpClientParser, + ExceptionListener exceptionListener, int bufferSize, ByteOrder byteOrder); +} diff --git a/shared/src/net/warsmash/nio/channels/KeyAttachment.java b/shared/src/net/warsmash/nio/channels/KeyAttachment.java new file mode 100644 index 0000000..c8abd13 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/KeyAttachment.java @@ -0,0 +1,5 @@ +package net.warsmash.nio.channels; + +public interface KeyAttachment { + void selected(); +} diff --git a/shared/src/net/warsmash/nio/channels/OpenedChannel.java b/shared/src/net/warsmash/nio/channels/OpenedChannel.java new file mode 100644 index 0000000..ed16a33 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/OpenedChannel.java @@ -0,0 +1,5 @@ +package net.warsmash.nio.channels; + +public interface OpenedChannel { + void close(); +} diff --git a/shared/src/net/warsmash/nio/channels/SelectableChannelOpener.java b/shared/src/net/warsmash/nio/channels/SelectableChannelOpener.java new file mode 100644 index 0000000..8738f0c --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/SelectableChannelOpener.java @@ -0,0 +1,150 @@ +package net.warsmash.nio.channels; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Iterator; +import java.util.Set; + +import net.warsmash.networking.udp.UdpServerListener; +import net.warsmash.nio.channels.tcp.TCPClientKeyAttachment; +import net.warsmash.nio.channels.tcp.TCPClientParser; +import net.warsmash.nio.channels.tcp.TCPServerKeyAttachment; +import net.warsmash.nio.channels.udp.UDPServerKeyAttachment; +import net.warsmash.nio.util.ExceptionListener; + +public class SelectableChannelOpener implements ChannelOpener { + + private Selector selector; + private int openChannelCount; + private final ChannelListener channelListener; + + public SelectableChannelOpener() { + this.channelListener = new ClosedChannelListenerImpl(); + } + + private void ensureSelectorOpen() throws IOException { + if (this.selector == null) { + this.selector = SelectorProvider.provider().openSelector(); + } + } + + @Override + public OpenedChannel openUDPServerChannel(final int port, final UdpServerListener listener, + final ExceptionListener exceptionListener, final int bufferSize, final ByteOrder byteOrder) { + try { + ensureSelectorOpen(); + final DatagramChannel channel = DatagramChannel.open().bind(new InetSocketAddress(port)); + channel.configureBlocking(false); + final ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize); + readBuffer.order(byteOrder); + final UDPServerKeyAttachment udpServerKeyAttachment = new UDPServerKeyAttachment(readBuffer, listener, + channel, exceptionListener, this.channelListener); + udpServerKeyAttachment.setKey(channel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, + udpServerKeyAttachment)); + this.channelListener.channelOpened(); + return udpServerKeyAttachment; + } + catch (final IOException e) { + throw new RuntimeException("Exception while opening channel", e); + } + } + + @Override + public OpenedChannel openTCPServerChannel(final int port, final SocketChannelCallback callback, + final ExceptionListener exceptionListener, final int bufferSize, final ByteOrder byteOrder) { + try { + ensureSelectorOpen(); + final ServerSocketChannel channel = ServerSocketChannel.open().bind(new InetSocketAddress(port)); + channel.configureBlocking(false); + final TCPServerKeyAttachment tcpServerKeyAttachment = new TCPServerKeyAttachment(this.selector, channel, + callback, exceptionListener, this.channelListener, bufferSize, byteOrder); + tcpServerKeyAttachment + .setKey(channel.register(this.selector, SelectionKey.OP_ACCEPT, tcpServerKeyAttachment)); + this.channelListener.channelOpened(); + return tcpServerKeyAttachment; + } + catch (final IOException e) { + throw new RuntimeException("Exception while opening channel", e); + } + } + + @Override + public WritableOutput openTCPClientChannel(final SocketAddress socketAddress, final TCPClientParser tcpClientParser, + final ExceptionListener exceptionListener, final int bufferSize, final ByteOrder byteOrder) { + try { + ensureSelectorOpen(); + final SocketChannel channel = SocketChannel.open(); + final boolean connected = channel.connect(socketAddress); + channel.configureBlocking(false); + final ByteBuffer readBuffer = ByteBuffer.allocate(bufferSize).order(byteOrder); + final ByteBuffer writeBuffer = ByteBuffer.allocate(bufferSize).order(byteOrder); + final TCPClientKeyAttachment keyAttachment = new TCPClientKeyAttachment(channel, exceptionListener, + this.channelListener, readBuffer, writeBuffer); + keyAttachment.setParser(tcpClientParser); + if (connected) { + final SelectionKey key = channel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, + keyAttachment); + keyAttachment.setKey(key); + } + else { + throw new IllegalStateException("Not connected"); + } + this.channelListener.channelOpened(); + return keyAttachment; + } + catch (final IOException e) { + throw new RuntimeException("Exception while opening channel", e); + } + } + + public void select(final int timeout) { + try { + final int selectedKeyCount = this.selector.select(timeout); + if (selectedKeyCount > 0) { + final Set selectedKeys = this.selector.selectedKeys(); + + final Iterator keyIterator = selectedKeys.iterator(); + + while (keyIterator.hasNext()) { + final SelectionKey key = keyIterator.next(); + ((KeyAttachment) key.attachment()).selected(); + keyIterator.remove(); + } + } + } + catch (final IOException e) { + System.err.println("Error reading from channel:"); + e.printStackTrace(); + } + } + + private final class ClosedChannelListenerImpl implements ChannelListener { + @Override + public void channelClosed() { + SelectableChannelOpener.this.openChannelCount--; + if (SelectableChannelOpener.this.openChannelCount == 0) { + try { + SelectableChannelOpener.this.selector.close(); + } + catch (final IOException e) { + throw new RuntimeException(e); + } + SelectableChannelOpener.this.selector = null; + } + } + + @Override + public void channelOpened() { + SelectableChannelOpener.this.openChannelCount++; + } + } +} diff --git a/shared/src/net/warsmash/nio/channels/SocketChannelCallback.java b/shared/src/net/warsmash/nio/channels/SocketChannelCallback.java new file mode 100644 index 0000000..f6d46d2 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/SocketChannelCallback.java @@ -0,0 +1,9 @@ +package net.warsmash.nio.channels; + +import java.net.SocketAddress; + +import net.warsmash.nio.channels.tcp.TCPClientParser; + +public interface SocketChannelCallback { + TCPClientParser onConnect(WritableOutput writableOpenedChannel, SocketAddress remoteAddress); +} diff --git a/shared/src/net/warsmash/nio/channels/TCPParser.java b/shared/src/net/warsmash/nio/channels/TCPParser.java new file mode 100644 index 0000000..c553a3e --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/TCPParser.java @@ -0,0 +1,8 @@ +package net.warsmash.nio.channels; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; + +public interface TCPParser { + void parse(SocketAddress sourceAddress, ByteBuffer data); +} diff --git a/shared/src/net/warsmash/nio/channels/WritableOutput.java b/shared/src/net/warsmash/nio/channels/WritableOutput.java new file mode 100644 index 0000000..a64d0c1 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/WritableOutput.java @@ -0,0 +1,7 @@ +package net.warsmash.nio.channels; + +import java.nio.ByteBuffer; + +public interface WritableOutput extends OpenedChannel { + void write(ByteBuffer data); +} diff --git a/shared/src/net/warsmash/nio/channels/tcp/ConnectionFinishingKeyAttachment.java b/shared/src/net/warsmash/nio/channels/tcp/ConnectionFinishingKeyAttachment.java new file mode 100644 index 0000000..0930b4e --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/tcp/ConnectionFinishingKeyAttachment.java @@ -0,0 +1,42 @@ +package net.warsmash.nio.channels.tcp; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +import net.warsmash.nio.channels.KeyAttachment; +import net.warsmash.nio.util.ExceptionListener; + +public class ConnectionFinishingKeyAttachment implements KeyAttachment { + private final Selector selector; + private final SocketChannel channel; + private final ExceptionListener exceptionListener; + private final KeyAttachment delegate; + private SelectionKey key; + + public ConnectionFinishingKeyAttachment(final Selector selector, final SocketChannel channel, + final ExceptionListener exceptionListener, final KeyAttachment delegate) { + this.selector = selector; + this.channel = channel; + this.exceptionListener = exceptionListener; + this.delegate = delegate; + } + + public void setKey(final SelectionKey key) { + this.key = key; + } + + @Override + public void selected() { + try { + this.channel.finishConnect(); + System.out.println("finishConnect()"); + this.key.cancel(); +// delegate.setKey(channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, delegate)); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } +} diff --git a/shared/src/net/warsmash/nio/channels/tcp/TCPClientKeyAttachment.java b/shared/src/net/warsmash/nio/channels/tcp/TCPClientKeyAttachment.java new file mode 100644 index 0000000..37aa6a3 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/tcp/TCPClientKeyAttachment.java @@ -0,0 +1,99 @@ +package net.warsmash.nio.channels.tcp; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; + +import net.warsmash.nio.channels.ChannelListener; +import net.warsmash.nio.channels.KeyAttachment; +import net.warsmash.nio.channels.WritableOutput; +import net.warsmash.nio.util.ExceptionListener; + +public class TCPClientKeyAttachment implements KeyAttachment, WritableOutput { + private TCPClientParser parser; + private final SocketChannel channel; + private final ExceptionListener exceptionListener; + private final ChannelListener channelListener; + private final ByteBuffer readBuffer; + private final ByteBuffer writeBuffer; + private SelectionKey key; + + public TCPClientKeyAttachment(final SocketChannel channel, final ExceptionListener exceptionListener, + final ChannelListener channelListener, final ByteBuffer readBuffer, final ByteBuffer writeBuffer) { + this.channel = channel; + this.exceptionListener = exceptionListener; + this.channelListener = channelListener; + this.readBuffer = readBuffer; + this.writeBuffer = writeBuffer; + } + + public void setParser(final TCPClientParser parser) { + this.parser = parser; + } + + public void setKey(final SelectionKey key) { + this.key = key; + } + + @Override + public void selected() { + if (this.key.isReadable()) { + this.readBuffer.clear(); + try { + final int nRead = this.channel.read(this.readBuffer); + if (nRead > 0) { + this.readBuffer.flip(); + this.parser.parse(this.readBuffer); + } + else if (nRead == -1) { + this.parser.disconnected(); + this.channel.close(); + this.channelListener.channelClosed(); + } + else { + throw new IOException("Did not read bytes"); + } + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + if (this.key.isWritable()) { + this.writeBuffer.flip(); + try { + this.channel.write(this.writeBuffer); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + this.writeBuffer.compact(); + } + // TODO if isWritable + } + + @Override + public void close() { + try { + this.channel.close(); + this.channelListener.channelClosed(); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + + @Override + public void write(final ByteBuffer data) { + final int bytesWanted = data.remaining(); + try { + if (this.channel.write(data) < bytesWanted) { + this.writeBuffer.put(data); + } + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + +} diff --git a/shared/src/net/warsmash/nio/channels/tcp/TCPClientParser.java b/shared/src/net/warsmash/nio/channels/tcp/TCPClientParser.java new file mode 100644 index 0000000..09855ee --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/tcp/TCPClientParser.java @@ -0,0 +1,7 @@ +package net.warsmash.nio.channels.tcp; + +import net.warsmash.nio.channels.ByteParser; + +public interface TCPClientParser extends ByteParser { + void disconnected(); +} diff --git a/shared/src/net/warsmash/nio/channels/tcp/TCPServerKeyAttachment.java b/shared/src/net/warsmash/nio/channels/tcp/TCPServerKeyAttachment.java new file mode 100644 index 0000000..54a266c --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/tcp/TCPServerKeyAttachment.java @@ -0,0 +1,79 @@ +package net.warsmash.nio.channels.tcp; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import net.warsmash.nio.channels.ChannelListener; +import net.warsmash.nio.channels.KeyAttachment; +import net.warsmash.nio.channels.OpenedChannel; +import net.warsmash.nio.channels.SocketChannelCallback; +import net.warsmash.nio.util.ExceptionListener; + +public class TCPServerKeyAttachment implements KeyAttachment, OpenedChannel { + private final ServerSocketChannel channel; + private final SocketChannelCallback callback; + private final ExceptionListener exceptionListener; + private final Selector selector; + private final ChannelListener channelListener; + private final int bufferSize; + private final ByteOrder byteOrder; + private SelectionKey key; + + public TCPServerKeyAttachment(final Selector selector, final ServerSocketChannel channel, + final SocketChannelCallback callback, final ExceptionListener exceptionListener, + final ChannelListener channelListener, final int bufferSize, final ByteOrder byteOrder) { + this.selector = selector; + this.channel = channel; + this.callback = callback; + this.exceptionListener = exceptionListener; + this.channelListener = channelListener; + this.bufferSize = bufferSize; + this.byteOrder = byteOrder; + } + + public void setKey(final SelectionKey key) { + this.key = key; + } + + @Override + public void selected() { + if (this.key.isAcceptable()) { + try { + final SocketChannel socketChannel = this.channel.accept(); + socketChannel.configureBlocking(false); + final ByteBuffer readBuffer = ByteBuffer.allocate(this.bufferSize); + readBuffer.order(this.byteOrder); + final ByteBuffer writeBuffer = ByteBuffer.allocate(this.bufferSize); + writeBuffer.order(this.byteOrder); + final TCPClientKeyAttachment tcpServerClientKeyAttachment = new TCPClientKeyAttachment(socketChannel, + this.exceptionListener, this.channelListener, readBuffer, writeBuffer); + final TCPClientParser parser = this.callback.onConnect(tcpServerClientKeyAttachment, + socketChannel.getRemoteAddress()); + tcpServerClientKeyAttachment.setParser(parser); + tcpServerClientKeyAttachment.setKey(socketChannel.register(this.selector, + SelectionKey.OP_READ | SelectionKey.OP_WRITE, tcpServerClientKeyAttachment)); + this.channelListener.channelOpened(); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + } + + @Override + public void close() { + try { + this.channel.close(); + this.channelListener.channelClosed(); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + +} diff --git a/shared/src/net/warsmash/nio/channels/udp/UDPServerKeyAttachment.java b/shared/src/net/warsmash/nio/channels/udp/UDPServerKeyAttachment.java new file mode 100644 index 0000000..b5f6c06 --- /dev/null +++ b/shared/src/net/warsmash/nio/channels/udp/UDPServerKeyAttachment.java @@ -0,0 +1,63 @@ +package net.warsmash.nio.channels.udp; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; +import java.nio.channels.SelectionKey; + +import net.warsmash.networking.udp.UdpServerListener; +import net.warsmash.nio.channels.ChannelListener; +import net.warsmash.nio.channels.KeyAttachment; +import net.warsmash.nio.channels.OpenedChannel; +import net.warsmash.nio.util.ExceptionListener; + +public class UDPServerKeyAttachment implements KeyAttachment, OpenedChannel { + private final ByteBuffer readBuffer; + private final UdpServerListener serverListener; + private final DatagramChannel channel; + private final ExceptionListener exceptionListener; + private final ChannelListener channelListener; + private SelectionKey key; + + public UDPServerKeyAttachment(final ByteBuffer readBuffer, final UdpServerListener serverListener, + final DatagramChannel channel, final ExceptionListener exceptionListener, + final ChannelListener channelListener) { + this.readBuffer = readBuffer; + this.serverListener = serverListener; + this.channel = channel; + this.exceptionListener = exceptionListener; + this.channelListener = channelListener; + } + + public void setKey(final SelectionKey key) { + this.key = key; + } + + @Override + public void selected() { + if (this.key.isReadable()) { + this.readBuffer.clear(); + SocketAddress receiveAddr; + try { + receiveAddr = this.channel.receive(this.readBuffer); + this.readBuffer.flip(); + this.serverListener.parse(receiveAddr, this.readBuffer); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } + } + + @Override + public void close() { + try { + this.channel.close(); + this.channelListener.channelClosed(); + } + catch (final IOException e) { + this.exceptionListener.caught(e); + } + } +} diff --git a/shared/src/net/warsmash/nio/util/Callback.java b/shared/src/net/warsmash/nio/util/Callback.java new file mode 100644 index 0000000..c0acff7 --- /dev/null +++ b/shared/src/net/warsmash/nio/util/Callback.java @@ -0,0 +1,5 @@ +package net.warsmash.nio.util; + +public interface Callback { + void call(); +} diff --git a/shared/src/net/warsmash/nio/util/DisconnectListener.java b/shared/src/net/warsmash/nio/util/DisconnectListener.java new file mode 100644 index 0000000..85394ec --- /dev/null +++ b/shared/src/net/warsmash/nio/util/DisconnectListener.java @@ -0,0 +1,5 @@ +package net.warsmash.nio.util; + +public interface DisconnectListener { + void disconnected(); +} diff --git a/shared/src/net/warsmash/nio/util/ExceptionListener.java b/shared/src/net/warsmash/nio/util/ExceptionListener.java new file mode 100644 index 0000000..483c32c --- /dev/null +++ b/shared/src/net/warsmash/nio/util/ExceptionListener.java @@ -0,0 +1,12 @@ +package net.warsmash.nio.util; + +public interface ExceptionListener { + void caught(Exception e); + + ExceptionListener THROW_RUNTIME = new ExceptionListener() { + @Override + public void caught(final Exception e) { + throw new RuntimeException(e); + } + }; +} diff --git a/shared/src/net/warsmash/uberserver/AccountCreationFailureReason.java b/shared/src/net/warsmash/uberserver/AccountCreationFailureReason.java new file mode 100644 index 0000000..99620db --- /dev/null +++ b/shared/src/net/warsmash/uberserver/AccountCreationFailureReason.java @@ -0,0 +1,5 @@ +package net.warsmash.uberserver; + +public enum AccountCreationFailureReason { + USERNAME_ALREADY_EXISTS; +} diff --git a/shared/src/net/warsmash/uberserver/GamingNetwork.java b/shared/src/net/warsmash/uberserver/GamingNetwork.java new file mode 100644 index 0000000..6383eb1 --- /dev/null +++ b/shared/src/net/warsmash/uberserver/GamingNetwork.java @@ -0,0 +1,5 @@ +package net.warsmash.uberserver; + +public class GamingNetwork { + public static final int PORT = 6119; +} diff --git a/shared/src/net/warsmash/uberserver/GamingNetworkClientToServerListener.java b/shared/src/net/warsmash/uberserver/GamingNetworkClientToServerListener.java new file mode 100644 index 0000000..faeb30e --- /dev/null +++ b/shared/src/net/warsmash/uberserver/GamingNetworkClientToServerListener.java @@ -0,0 +1,26 @@ +package net.warsmash.uberserver; + +import net.warsmash.nio.util.DisconnectListener; + +public interface GamingNetworkClientToServerListener extends DisconnectListener { + void handshake(String gameId, int version); + + void createAccount(String username, String password); + + void login(String username, String password); + + void joinChannel(String channelName); + + void chatMessage(String text); + + void emoteMessage(String text); + + class Protocol { + public static final int HANDSHAKE = 1; + public static final int CREATE_ACCOUNT = 2; + public static final int LOGIN = 3; + public static final int JOIN_CHANNEL = 4; + public static final int CHAT_MESSAGE = 5; + public static final int EMOTE_MESSAGE = 6; + } +} diff --git a/shared/src/net/warsmash/uberserver/GamingNetworkServerToClientListener.java b/shared/src/net/warsmash/uberserver/GamingNetworkServerToClientListener.java new file mode 100644 index 0000000..9123f94 --- /dev/null +++ b/shared/src/net/warsmash/uberserver/GamingNetworkServerToClientListener.java @@ -0,0 +1,24 @@ +package net.warsmash.uberserver; + +public interface GamingNetworkServerToClientListener { + void handshakeAccepted(); + + void handshakeDenied(HandshakeDeniedReason reason); + + void accountCreationOk(); + + void accountCreationFailed(AccountCreationFailureReason reason); + + void loginOk(String welcomeMessage); + + void joinedChannel(String channelName); + + class Protocol { + public static final int HANDSHAKE_ACCEPTED = 1; + public static final int HANDSHAKE_DENIED = 2; + public static final int ACCOUNT_CREATION_OK = 3; + public static final int ACCOUNT_CREATION_FAILED = 4; + public static final int LOGIN_OK = 5; + public static final int JOINED_CHANNEL = 6; + } +} diff --git a/shared/src/net/warsmash/uberserver/HandshakeDeniedReason.java b/shared/src/net/warsmash/uberserver/HandshakeDeniedReason.java new file mode 100644 index 0000000..cb9562c --- /dev/null +++ b/shared/src/net/warsmash/uberserver/HandshakeDeniedReason.java @@ -0,0 +1,7 @@ +package net.warsmash.uberserver; + +public enum HandshakeDeniedReason { + BAD_GAME, + BAD_GAME_VERSION, + SERVER_ERROR; +}