diff --git a/core/src/gdx/diablo/Colors.java b/core/src/gdx/diablo/Colors.java index 67f7b32f..4e5afddc 100644 --- a/core/src/gdx/diablo/Colors.java +++ b/core/src/gdx/diablo/Colors.java @@ -31,6 +31,7 @@ public class Colors { public Color c10 = C10.cpy(); public Color purple = PURPLE.cpy(); public Color c12 = C12.cpy(); + public Color modal = black.cpy().sub(0, 0, 0, 0.5f); public Colors() {} diff --git a/core/src/gdx/diablo/graphics/PaletteIndexedColorDrawable.java b/core/src/gdx/diablo/graphics/PaletteIndexedColorDrawable.java new file mode 100644 index 00000000..9f757573 --- /dev/null +++ b/core/src/gdx/diablo/graphics/PaletteIndexedColorDrawable.java @@ -0,0 +1,47 @@ +package gdx.diablo.graphics; + +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; + +import gdx.diablo.BlendMode; +import gdx.diablo.Diablo; + +public class PaletteIndexedColorDrawable extends TextureRegionDrawable { + + public Color tint; + + public PaletteIndexedColorDrawable(Color color) { + super(Diablo.textures.white); + this.tint = color; + } + + @Override + public void draw(Batch batch, float x, float y, float width, float height) { + if (!(batch instanceof PaletteIndexedBatch)) { + // unsupported, will draw white for now + super.draw(batch, x, y, width, height); + return; + } + + PaletteIndexedBatch b = (PaletteIndexedBatch) batch; + b.setBlendMode(BlendMode.SOLID, tint); + super.draw(batch, x, y, width, height); + b.resetBlendMode(); + } + + @Override + public void draw(Batch batch, float x, float y, float originX, float originY, float width, + float height, float scaleX, float scaleY, float rotation) { + if (!(batch instanceof PaletteIndexedBatch)) { + // unsupported, will draw white for now + super.draw(batch, x, y, originX, originY, width, height, scaleX, scaleY, rotation); + return; + } + + PaletteIndexedBatch b = (PaletteIndexedBatch) batch; + b.setBlendMode(BlendMode.SOLID, tint); + super.draw(batch, x, y, originX, originY, width, height, scaleX, scaleY, rotation); + b.resetBlendMode(); + } +} diff --git a/core/src/gdx/diablo/panel/EscapePanel.java b/core/src/gdx/diablo/panel/EscapePanel.java index 8526868a..e1e97cff 100644 --- a/core/src/gdx/diablo/panel/EscapePanel.java +++ b/core/src/gdx/diablo/panel/EscapePanel.java @@ -46,7 +46,7 @@ public class EscapePanel extends WidgetGroup implements Disposable { exit.addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { - Diablo.client.setScreen(new MenuScreen()); + Diablo.client.clearAndSet(new MenuScreen()); } }); diff --git a/core/src/gdx/diablo/panel/MobilePanel.java b/core/src/gdx/diablo/panel/MobilePanel.java index 84538fde..5e7a7d23 100644 --- a/core/src/gdx/diablo/panel/MobilePanel.java +++ b/core/src/gdx/diablo/panel/MobilePanel.java @@ -46,7 +46,7 @@ public class MobilePanel extends Table implements Disposable { } else if (actor == btnMap) { } else if (actor == btnMessages) { - + gameScreen.input.setVisible(!gameScreen.input.isVisible()); } else if (actor == btnQuests) { } else if (actor == btnEscapeMenu) { @@ -94,13 +94,13 @@ public class MobilePanel extends Table implements Disposable { down = new TextureRegionDrawable(Diablo.assets.get(minipanelbtnDescriptor).getTexture(15)); }}); btnEscapeMenu.addListener(clickListener); - final float size = 40; + final float size = 50; add(btnCharacter).size(size); add(btnInventory).size(size); add(btnSkillTree).size(size); //add(btnParty).size(size); //add(btnMap).size(size); - //add(btnMessages).size(size); + add(btnMessages).size(size); //add(btnQuests).size(size); add(btnEscapeMenu).size(size); pack(); diff --git a/core/src/gdx/diablo/screen/GameScreen.java b/core/src/gdx/diablo/screen/GameScreen.java index 60525ed5..1b3bf272 100644 --- a/core/src/gdx/diablo/screen/GameScreen.java +++ b/core/src/gdx/diablo/screen/GameScreen.java @@ -13,19 +13,29 @@ import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.GridPoint2; import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.net.Socket; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Touchpad; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; import com.badlogic.gdx.scenes.scene2d.utils.UIUtils; +import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Timer; +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; + import gdx.diablo.Diablo; import gdx.diablo.Keys; import gdx.diablo.entity3.Player; import gdx.diablo.graphics.PaletteIndexedBatch; +import gdx.diablo.graphics.PaletteIndexedColorDrawable; import gdx.diablo.key.MappedKey; import gdx.diablo.key.MappedKeyStateAdapter; import gdx.diablo.map.Map; @@ -37,6 +47,8 @@ import gdx.diablo.panel.EscapePanel; import gdx.diablo.panel.InventoryPanel; import gdx.diablo.panel.MobilePanel; import gdx.diablo.panel.StashPanel; +import gdx.diablo.server.PipedSocket; +import gdx.diablo.widget.TextArea; public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable { private static final String TAG = "GameScreen"; @@ -68,12 +80,17 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable OrthographicCamera camera; InputProcessor inputProcessorTest; - //TextArea input; + public TextArea input; + TextArea output; //Char character; public Player player; Timer.Task updateTask; + Socket socket; + PrintWriter out; + BufferedReader in; + @Override public Array getDependencies() { Array dependencies = new Array<>(); @@ -82,22 +99,37 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable return dependencies; } - //public GameScreen(final Char character) { - public GameScreen(final Player player) { - this.player = player; + public GameScreen(Player player) { + this(player, new PipedSocket()); + } + + //public GameScreen(final Char character) { + public GameScreen(final Player player, Socket socket) { + this.player = player; + this.socket = socket; - /* input = new TextArea("", new TextArea.TextFieldStyle() {{ this.font = Diablo.fonts.fontformal12; this.fontColor = Diablo.colors.white; - this.background = new TextureRegionDrawable(Diablo.textures.modal); + this.background = new PaletteIndexedColorDrawable(Diablo.colors.modal); this.cursor = new TextureRegionDrawable(Diablo.textures.white); }}); input.setSize(Diablo.VIRTUAL_WIDTH * 0.75f, Diablo.fonts.fontformal12.getLineHeight() * 3); input.setPosition(Diablo.VIRTUAL_WIDTH_CENTER - input.getWidth() / 2, 100); input.setAlignment(Align.topLeft); input.setVisible(false); - */ + + output = new TextArea("", new TextArea.TextFieldStyle() {{ + this.font = Diablo.fonts.fontformal12; + this.fontColor = Diablo.colors.white; + this.cursor = new TextureRegionDrawable(Diablo.textures.white); + }}); + output.setDebug(true); + output.setSize(Diablo.VIRTUAL_WIDTH * 0.75f, Diablo.fonts.fontformal12.getLineHeight() * 8); + output.setPosition(10, Diablo.VIRTUAL_HEIGHT - 10, Align.topLeft); + output.setAlignment(Align.topLeft); + output.setDisabled(true); + output.setVisible(true); escapePanel = new EscapePanel(); @@ -130,14 +162,17 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable stage = new Stage(Diablo.viewport, Diablo.batch); if (mobilePanel != null) stage.addActor(mobilePanel); - //stage.addActor(input); + stage.addActor(input); + stage.addActor(output); stage.addActor(controlPanel); stage.addActor(escapePanel); stage.addActor(inventoryPanel); stage.addActor(characterPanel); stage.addActor(stashPanel); controlPanel.toFront(); - //input.toFront(); + if (mobilePanel != null) mobilePanel.toFront(); + output.toFront(); + input.toFront(); escapePanel.toFront(); if (Gdx.app.getType() == Application.ApplicationType.Android || DEBUG_TOUCHPAD) { @@ -187,12 +222,13 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable } else { escapePanel.setVisible(true); } - /*} else if (key == Keys.Enter) { + } else if (key == Keys.Enter) { boolean visible = !input.isVisible(); if (!visible) { String text = input.getText(); if (!text.isEmpty()) { Gdx.app.debug(TAG, text); + out.println(text); input.setText(""); } } @@ -200,7 +236,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable input.setVisible(visible); if (visible) { stage.setKeyboardFocus(input); - }*/ + } } else if (key == Keys.Inventory) { inventoryPanel.setVisible(!inventoryPanel.isVisible()); } else if (key == Keys.Character) { @@ -263,6 +299,15 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable @Override public void render(float delta) { + try { + for (String str; in.ready() && (str = in.readLine()) != null;) { + output.appendText(str); + output.appendText("\n"); + } + } catch (IOException e) { + Gdx.app.error(TAG, e.getMessage()); + } + PaletteIndexedBatch b = Diablo.batch; b.setPalette(Diablo.palettes.act1); @@ -334,10 +379,21 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable mapRenderer.setPosition(player.origin()); } }, 0, 1 / 25f); + + if (socket != null && socket.isConnected()) { + Gdx.app.log(TAG, "connecting to " + socket.getRemoteAddress() + "..."); + in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); + out = new PrintWriter(socket.getOutputStream(), true); + } } @Override public void hide() { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(out); + socket.dispose(); + Gdx.app.log(TAG, "Disposing socket... " + socket.isConnected()); + Keys.Esc.removeStateListener(mappedKeyStateListener); Keys.Inventory.removeStateListener(mappedKeyStateListener); Keys.Character.removeStateListener(mappedKeyStateListener); diff --git a/core/src/gdx/diablo/screen/LobbyScreen.java b/core/src/gdx/diablo/screen/LobbyScreen.java index c9479902..d20d71e2 100644 --- a/core/src/gdx/diablo/screen/LobbyScreen.java +++ b/core/src/gdx/diablo/screen/LobbyScreen.java @@ -8,6 +8,7 @@ import com.badlogic.gdx.assets.AssetDescriptor; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.net.HttpRequestBuilder; import com.badlogic.gdx.net.Socket; +import com.badlogic.gdx.net.SocketHints; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.EventListener; import com.badlogic.gdx.scenes.scene2d.Group; @@ -38,6 +39,7 @@ import java.net.SocketTimeoutException; import gdx.diablo.Diablo; import gdx.diablo.codec.DC6; +import gdx.diablo.entity3.Player; import gdx.diablo.graphics.PaletteIndexedBatch; import gdx.diablo.loader.DC6Loader; import gdx.diablo.server.Account; @@ -80,6 +82,9 @@ public class LobbyScreen extends ScreenAdapter { private Stage stage; + private Account account; + private Player player; + private TextArea taChatOutput; private TextField tfChatInput; @@ -87,7 +92,10 @@ public class LobbyScreen extends ScreenAdapter { private PrintWriter out; private BufferedReader in; - public LobbyScreen(Account account) { + public LobbyScreen(Account account, Player player) { + this.account = account; + this.player = player; + Diablo.assets.load(waitingroombkgdDescriptor); Diablo.assets.load(blankbckgDescriptor); Diablo.assets.load(creategamebckgDescriptor); @@ -284,16 +292,16 @@ public class LobbyScreen extends ScreenAdapter { public void handleHttpResponse(Net.HttpResponse httpResponse) { String response = httpResponse.getResultAsString(); try { - Session session = new Json().fromJson(Session.class, response); + final Session session = new Json().fromJson(Session.class, response); Gdx.app.log(TAG, "create-session " + response); - - Socket socket = null; - try { - socket = Gdx.net.newClientSocket(Net.Protocol.TCP, session.host, session.port, null); - Gdx.app.log(TAG, "create-session connect " + session.host + ":" + session.port + " " + socket.isConnected()); - } finally { - if (socket != null) socket.dispose(); - } + Gdx.app.postRunnable(new Runnable() { + @Override + public void run() { + Socket socket = Gdx.net.newClientSocket(Net.Protocol.TCP, session.host, session.port, null); + Gdx.app.log(TAG, "create-session connect " + session.host + ":" + session.port + " " + socket.isConnected()); + Diablo.client.pushScreen(new LoadingScreen(new GameScreen(player, socket))); + } + }); } catch (SerializationException e) { SessionError error = new Json().fromJson(SessionError.class, response); Gdx.app.log(TAG, "create-session " + error.toString()); @@ -404,7 +412,24 @@ public class LobbyScreen extends ScreenAdapter { public void changed(ChangeEvent event, Actor actor) { list.getSelection().setRequired(true); btnJoinGame.setDisabled(list.getSelection().isEmpty()); - tfGameName.setText(list.getSelected().toString()); + Session selected = list.getSelected(); + tfGameName.setText(selected != null ? selected.toString() : ""); + } + }); + btnJoinGame.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + if (btnJoinGame.isDisabled()) return; + final Session session = list.getSelected(); + Gdx.app.log(TAG, "join-session " + session); + Gdx.app.postRunnable(new Runnable() { + @Override + public void run() { + Socket socket = Gdx.net.newClientSocket(Net.Protocol.TCP, session.host, session.port, null); + Gdx.app.log(TAG, "join-session connect " + session.host + ":" + session.port + " " + socket.isConnected()); + Diablo.client.pushScreen(new LoadingScreen(new GameScreen(player, socket))); + } + }); } }); @@ -499,15 +524,20 @@ public class LobbyScreen extends ScreenAdapter { } private void connect() { - try { - socket = Gdx.net.newClientSocket(Net.Protocol.TCP, "hydra", 6113, null); - in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); - out = new PrintWriter(socket.getOutputStream(), true); - } catch (Throwable t) { - Gdx.app.error(TAG, t.getMessage()); - taChatOutput.appendText(t.getMessage()); - taChatOutput.appendText("\n"); - } + new Thread(new Runnable() { + @Override + public void run() { + try { + socket = Gdx.net.newClientSocket(Net.Protocol.TCP, "hydra", 6113, new SocketHints()); + in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); + out = new PrintWriter(socket.getOutputStream(), true); + } catch (Throwable t) { + Gdx.app.error(TAG, t.getMessage()); + taChatOutput.appendText(t.getMessage()); + taChatOutput.appendText("\n"); + } + } + }).start(); } @Override diff --git a/core/src/gdx/diablo/screen/LoginScreen.java b/core/src/gdx/diablo/screen/LoginScreen.java index 46f1fbb6..d085817f 100644 --- a/core/src/gdx/diablo/screen/LoginScreen.java +++ b/core/src/gdx/diablo/screen/LoginScreen.java @@ -125,7 +125,7 @@ public class LoginScreen extends ScreenAdapter { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - Diablo.client.pushScreen(new LobbyScreen(account)); + Diablo.client.pushScreen(new SelectCharacterScreen2(account)); } }); } diff --git a/core/src/gdx/diablo/screen/SelectCharacterScreen.java b/core/src/gdx/diablo/screen/SelectCharacterScreen.java index ec6e1e5c..08c441fd 100644 --- a/core/src/gdx/diablo/screen/SelectCharacterScreen.java +++ b/core/src/gdx/diablo/screen/SelectCharacterScreen.java @@ -60,7 +60,8 @@ public class SelectCharacterScreen extends ScreenAdapter { Diablo.client.popScreen(); } else if (actor == btnOK) { assert selected != null; - Diablo.client.clearAndSet(new LoadingScreen(new GameScreen(new Player(selected.getD2S())))); + GameScreen game = new GameScreen(new Player(selected.getD2S())); + Diablo.client.clearAndSet(new LoadingScreen(game)); } else if (actor == btnCreateNewCharacter) { Diablo.client.pushScreen(new CreateCharacterScreen()); } diff --git a/core/src/gdx/diablo/screen/SelectCharacterScreen2.java b/core/src/gdx/diablo/screen/SelectCharacterScreen2.java new file mode 100644 index 00000000..20bd52e8 --- /dev/null +++ b/core/src/gdx/diablo/screen/SelectCharacterScreen2.java @@ -0,0 +1,171 @@ +package gdx.diablo.screen; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.ScreenAdapter; +import com.badlogic.gdx.assets.AssetDescriptor; +import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.InputEvent; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.ui.Button; +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; +import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable; +import com.badlogic.gdx.utils.Array; + +import gdx.diablo.Diablo; +import gdx.diablo.codec.D2S; +import gdx.diablo.codec.DC6; +import gdx.diablo.entity3.Player; +import gdx.diablo.graphics.PaletteIndexedBatch; +import gdx.diablo.loader.DC6Loader; +import gdx.diablo.server.Account; +import gdx.diablo.widget.SelectButton; +import gdx.diablo.widget.TextButton; + +public class SelectCharacterScreen2 extends ScreenAdapter { + private static final String TAG = "SelectCharacterScreen2"; + + final AssetDescriptor characterselectscreenEXPDescriptor = new AssetDescriptor<>("data\\global\\ui\\CharSelect\\charselectbckg.dc6", DC6.class, DC6Loader.DC6Parameters.COMBINE); + TextureRegion characterselectscreenEXP; + + final AssetDescriptor MediumButtonBlankDescriptor = new AssetDescriptor<>("data\\global\\ui\\FrontEnd\\MediumButtonBlank.dc6", DC6.class); + final AssetDescriptor TallButtonBlankDescriptor = new AssetDescriptor<>("data\\global\\ui\\CharSelect\\TallButtonBlank.dc6", DC6.class); + + private Stage stage; + private Button btnExit; + private Button btnOK; + private Button btnCreateNewCharacter; + + private SelectButton selected; + private Array characters; + + private Account account; + + public SelectCharacterScreen2(Account account) { + this.account = account; + SelectButton.load(); + Diablo.assets.load(characterselectscreenEXPDescriptor); + Diablo.assets.load(MediumButtonBlankDescriptor); + Diablo.assets.load(TallButtonBlankDescriptor); + stage = new Stage(Diablo.viewport, Diablo.batch); + } + + @Override + public void show() { + Diablo.assets.finishLoadingAsset(characterselectscreenEXPDescriptor); + characterselectscreenEXP = Diablo.assets.get(characterselectscreenEXPDescriptor).getTexture(); + + ChangeListener clickListener = new ChangeListener() { + @Override + public void changed(ChangeEvent event, Actor actor) { + if (actor == btnExit) { + Diablo.client.popScreen(); + } else if (actor == btnOK) { + assert selected != null; + Diablo.client.pushScreen(new LobbyScreen(account, new Player(selected.getD2S()))); + } else if (actor == btnCreateNewCharacter) { + //Diablo.client.pushScreen(new CreateCharacterScreen()); + } + } + }; + TextButton.TextButtonStyle tallButtonStyle = new TextButton.TextButtonStyle() {{ + Diablo.assets.finishLoadingAsset(TallButtonBlankDescriptor); + DC6 pages = Diablo.assets.get(TallButtonBlankDescriptor); + up = new TextureRegionDrawable(pages.getTexture(0)); + down = new TextureRegionDrawable(pages.getTexture(1)); + font = Diablo.fonts.fontexocet10; + }}; + btnCreateNewCharacter = new TextButton(5273, tallButtonStyle); + btnCreateNewCharacter.addListener(clickListener); + btnCreateNewCharacter.setPosition(Diablo.VIRTUAL_WIDTH - btnCreateNewCharacter.getWidth() - 100, 100); + + TextButton.TextButtonStyle mediumButtonStyle = new TextButton.TextButtonStyle() {{ + Diablo.assets.finishLoadingAsset(MediumButtonBlankDescriptor); + DC6 pages = Diablo.assets.get(MediumButtonBlankDescriptor); + up = new TextureRegionDrawable(pages.getTexture(0)); + down = new TextureRegionDrawable(pages.getTexture(1)); + font = Diablo.fonts.fontexocet10; + }}; + btnExit = new TextButton(5101, mediumButtonStyle); + btnExit.addListener(clickListener); + btnExit.setPosition(20, 20); + btnOK = new TextButton(5102, mediumButtonStyle); + btnOK.addListener(clickListener); + btnOK.setPosition(Diablo.VIRTUAL_WIDTH - 20 - btnOK.getWidth(), 20); + //btnOK.setVisible(false); + btnOK.setDisabled(true); + + FileHandle savesLocation = Diablo.home.child("Save"); + FileHandle[] saves = savesLocation.list(D2S.EXT); + characters = new Array<>(); + for (FileHandle save : saves) { + Gdx.app.debug(TAG, "Loading " + save.toString()); + D2S d2s = D2S.loadFromFile(save); + SelectButton button = new SelectButton(d2s); + button.addListener(new ClickListener() { + @Override + public void clicked(InputEvent event, float x, float y) { + if (getTapCount() >= 2) { + assert selected == event.getListenerActor(); + btnOK.toggle(); + return; + } + + if (selected != null) selected.deselect(); + selected = (SelectButton) event.getListenerActor(); + selected.select(); + } + }); + characters.add(button); + if (selected == null) { + selected = button; + selected.select(); + btnOK.setDisabled(false); + } + } + + for (int i = 0, x, y = Diablo.VIRTUAL_HEIGHT - 64 - SelectButton.HEIGHT; i < characters.size; i++) { + x = (i & 1) == 0 ? 64 : 64 + SelectButton.WIDTH; + SelectButton character = characters.get(i); + character.setPosition(x, y); + stage.addActor(character); + if ((i & 1) == 1) y -= SelectButton.HEIGHT; + } + + stage.addActor(btnExit); + stage.addActor(btnOK); + stage.addActor(btnCreateNewCharacter); + Diablo.input.addProcessor(stage); + } + + @Override + public void hide() { + Diablo.input.removeProcessor(stage); + } + + @Override + public void dispose() { + SelectButton.unload(); + for (SelectButton selectButton : characters) if (selected != selectButton) selectButton.dispose(); + Diablo.assets.unload(characterselectscreenEXPDescriptor.fileName); + Diablo.assets.unload(MediumButtonBlankDescriptor.fileName); + Diablo.assets.unload(TallButtonBlankDescriptor.fileName); + } + + @Override + public void render(float delta) { + PaletteIndexedBatch b = Diablo.batch; + b.begin(Diablo.palettes.units); + int x = Diablo.VIRTUAL_WIDTH_CENTER; + int y = Diablo.VIRTUAL_HEIGHT_CENTER; + int xOff = (characterselectscreenEXP.getRegionWidth() / 2); + int yOff = (characterselectscreenEXP.getRegionHeight() / 2); + b.draw(characterselectscreenEXP, x - xOff, Diablo.VIRTUAL_HEIGHT - characterselectscreenEXP.getRegionHeight()); + b.end(); + + stage.act(delta); + stage.draw(); + } +} diff --git a/core/src/gdx/diablo/server/PipedSocket.java b/core/src/gdx/diablo/server/PipedSocket.java new file mode 100644 index 00000000..70f84e0d --- /dev/null +++ b/core/src/gdx/diablo/server/PipedSocket.java @@ -0,0 +1,52 @@ +package gdx.diablo.server; + +import com.badlogic.gdx.net.Socket; +import com.badlogic.gdx.utils.GdxRuntimeException; + +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +public class PipedSocket implements Socket { + PipedInputStream in; + PipedOutputStream out; + + public PipedSocket() { + try { + in = new PipedInputStream(); + out = new PipedOutputStream(in); + } catch (IOException e) { + throw new GdxRuntimeException(e); + } + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public InputStream getInputStream() { + return in; + } + + @Override + public OutputStream getOutputStream() { + return out; + } + + @Override + public String getRemoteAddress() { + return "localhost"; + } + + @Override + public void dispose() { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(out); + } +} diff --git a/core/src/gdx/diablo/server/Server.java b/core/src/gdx/diablo/server/Server.java new file mode 100644 index 00000000..4ce340af --- /dev/null +++ b/core/src/gdx/diablo/server/Server.java @@ -0,0 +1,166 @@ +package gdx.diablo.server; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Net; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.net.ServerSocket; +import com.badlogic.gdx.net.Socket; +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.Json; + +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Server implements Disposable, Runnable { + private static final String TAG = "Server"; + + private final Json json = new Json(); + private List clients = new CopyOnWriteArrayList<>(); + + ThreadGroup clientThreads; + ServerSocket server; + AtomicBoolean kill; + Thread connectionListener; + int port; + String name; + + public Server(int port) { + this(port, ""); + } + + public Server(int port, String name) { + this.port = port; + this.name = name; + } + + @Override + public void run() { + clientThreads = new ThreadGroup(name + "-Clients"); + server = Gdx.net.newServerSocket(Net.Protocol.TCP, port, null); + kill = new AtomicBoolean(false); + connectionListener = new Thread(new Runnable() { + @Override + public void run() { + Gdx.app.log(name, "listening on " + port); + while (!kill.get()) { + try { + Socket socket = server.accept(null); + new Client(socket).start(); + Gdx.app.log(name, "connection from " + socket.getRemoteAddress()); + } catch (Throwable t) { + Gdx.app.log(name, t.getMessage(), t); + } + } + } + }); + connectionListener.setName("ConnectionListener"); + connectionListener.start(); + } + + @Override + public void dispose() { + for (Client client : clients) { + try { + client.socket.dispose(); + } catch (Throwable t) { + Gdx.app.error(name, t.getMessage(), t); + } + } + + kill.set(true); + //try { + // connectionListener.join(); + //} catch (InterruptedException ignored) {} + server.dispose(); + } + + public void update() { + /* + Socket socket; + BufferedReader in; + PrintWriter out; + for (ImmutableTriple client : clients) { + //System.out.println("client " + (client != null ? client.isConnected() : "false") + " " + client); + socket = client.left; + in = client.middle; + try { + if (!in.ready()) { + Gdx.app.log(name, socket.getRemoteAddress() + " disconnected"); + clients.remove(client); + socket.dispose(); + } + } catch (IOException e) { + Gdx.app.error(name, e.getMessage()); + } + + try { + for (String input; in.ready() && (input = in.readLine()) != null; ) { + Gdx.app.log(name, socket.getRemoteAddress() + ": " + input); + for (ImmutableTriple broadcast : clients) { + Gdx.app.log(name, "broadcast " + broadcast.left.getRemoteAddress() + ": " + input); + out = broadcast.right; + out.println(input); + if (out.checkError()) { + Gdx.app.log(name, "error at " + broadcast.left.getRemoteAddress()); + } + } + } + } catch (IOException e) { + Gdx.app.error(name, e.getMessage(), e); + } + } + */ + } + + private class Client extends Thread { + Socket socket; + BufferedReader in; + PrintWriter out; + + public Client(Socket socket) { + super(clientThreads, "Client-" + String.format("%08X", MathUtils.random(1, Integer.MAX_VALUE - 1))); + this.socket = socket; + } + + @Override + public void run() { + try { + in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); + out = new PrintWriter(socket.getOutputStream(), true); + + for (Client client : clients) { + client.out.println("CONNECT " + socket.getRemoteAddress()); + } + + clients.add(this); + + for (String input; (input = in.readLine()) != null; ) { + String message = "MESSAGE " + socket.getRemoteAddress() + ": " + input; + Gdx.app.log(getName(), message); + for (Client client : clients) { + client.out.println(message); + } + } + + } catch (Throwable t) { + Gdx.app.log(getName(), "ERROR " + socket.getRemoteAddress() + ": " + t.getMessage()); + } finally { + clients.remove(this); + String message = "DISCONNECT " + socket.getRemoteAddress(); + Gdx.app.log(getName(), message); + for (Client client : clients) { + client.out.println(message); + } + //IOUtils.closeQuietly(in); + IOUtils.closeQuietly(out); + if (socket != null) socket.dispose(); + } + } + } +} diff --git a/server/src/gdx/diablo/server/ChatServer.java b/server/src/gdx/diablo/server/ChatServer.java index b7601306..ad385a5b 100644 --- a/server/src/gdx/diablo/server/ChatServer.java +++ b/server/src/gdx/diablo/server/ChatServer.java @@ -94,7 +94,7 @@ public class ChatServer extends ApplicationAdapter { PrintWriter out; public Client(Socket socket) { - super(clientThreads, "Client-" + String.format("%08X", MathUtils.random(1, Integer.MAX_VALUE))); + super(clientThreads, "Client-" + String.format("%08X", MathUtils.random(1, Integer.MAX_VALUE - 1))); this.socket = socket; } @@ -103,6 +103,14 @@ public class ChatServer extends ApplicationAdapter { try { in = IOUtils.buffer(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); + + final Calendar calendar = Calendar.getInstance(); + DateFormat format = DateFormat.getDateTimeInstance(); + out.println("BNET " + format.format(calendar.getTime())); + for (PrintWriter client : clients) { + client.println("CONNECT " + socket.getRemoteAddress()); + } + clients.add(out); for (String input; (input = in.readLine()) != null; ) { diff --git a/server/src/gdx/diablo/server/DedicatedServer.java b/server/src/gdx/diablo/server/DedicatedServer.java new file mode 100644 index 00000000..ef60b95a --- /dev/null +++ b/server/src/gdx/diablo/server/DedicatedServer.java @@ -0,0 +1,43 @@ +package gdx.diablo.server; + +import com.badlogic.gdx.utils.Disposable; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class DedicatedServer extends Thread implements Disposable { + private static final String TAG = "DedicatedServer"; + + Server server; + AtomicBoolean kill; + + DedicatedServer(ThreadGroup group, String name, Server target) { + super(group, target, name); + server = target; + kill = new AtomicBoolean(false); + } + + public static DedicatedServer newDedicatedServer(ThreadGroup group, String name, int port) { + Server server = new Server(port, name); + return new DedicatedServer(group, name, server); + } + + @Override + public void run() { + super.run(); + while (!kill.get()) { + try { + Thread.sleep(100); + } catch (InterruptedException ignored) {} + server.update(); + } + } + + @Override + public void dispose() { + kill.set(true); + //try { + // join(); + //} catch (InterruptedException ignored) {} + server.dispose(); + } +} diff --git a/server/src/gdx/diablo/server/Server.java b/server/src/gdx/diablo/server/Server.java deleted file mode 100644 index 2d8cd5a3..00000000 --- a/server/src/gdx/diablo/server/Server.java +++ /dev/null @@ -1,39 +0,0 @@ -package gdx.diablo.server; - -import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.Net; -import com.badlogic.gdx.net.ServerSocket; -import com.badlogic.gdx.net.Socket; -import com.badlogic.gdx.utils.Json; - -import java.util.concurrent.atomic.AtomicBoolean; - -public class Server extends Thread { - private static final String TAG = "Server"; - - private final Json json = new Json(); - - ServerSocket server; - AtomicBoolean kill; - int port; - - Server(ThreadGroup group, String name, int port) { - super(group, name); - this.port = port; - kill = new AtomicBoolean(false); - } - - @Override - public void run() { - server = Gdx.net.newServerSocket(Net.Protocol.TCP, port, null); - while (!kill.get()) { - Socket client = null; - try { - client = server.accept(null); - Gdx.app.log(getName(), "connection from " + client.getRemoteAddress()); - } finally { - if (client != null) client.dispose(); - } - } - } -} diff --git a/server/src/gdx/diablo/server/ServerBrowser.java b/server/src/gdx/diablo/server/ServerBrowser.java index d1aa7374..a299c848 100644 --- a/server/src/gdx/diablo/server/ServerBrowser.java +++ b/server/src/gdx/diablo/server/ServerBrowser.java @@ -147,8 +147,8 @@ public class ServerBrowser extends ApplicationAdapter { session.port = 6114 + sessions.size(); sessions.put(session.getName(), session); - String id = String.format("%08x", MathUtils.random(Integer.MAX_VALUE - 1)); - Server server = new Server(sessionGroup, "Session-" + id, session.port); + String id = String.format("%08X", MathUtils.random(1, Integer.MAX_VALUE - 1)); + DedicatedServer server = DedicatedServer.newDedicatedServer(sessionGroup, "Session-" + id, session.port); server.start(); servers.put(session.getName(), server); diff --git a/server/test/gdx/diablo/server/ServerTest.java b/server/test/gdx/diablo/server/ServerTest.java index 4603e0b0..9189c4b9 100644 --- a/server/test/gdx/diablo/server/ServerTest.java +++ b/server/test/gdx/diablo/server/ServerTest.java @@ -5,12 +5,16 @@ import com.badlogic.gdx.Net; import com.badlogic.gdx.backends.headless.HeadlessApplication; import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration; import com.badlogic.gdx.net.HttpRequestBuilder; +import com.badlogic.gdx.net.Socket; +import com.badlogic.gdx.utils.Json; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ObjectUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; +import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; public class ServerTest { @@ -19,7 +23,7 @@ public class ServerTest { @Before public void setUp() throws Exception { HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration(); - new HeadlessApplication(new Server(), config); + new HeadlessApplication(new ServerBrowser(), config); } @After @@ -122,4 +126,103 @@ public class ServerTest { }); while (!done.get()) ; } + + @Test + public void testConnect() { + final AtomicBoolean done = new AtomicBoolean(); + Net.HttpRequest request = new HttpRequestBuilder() + .newRequest() + .method(Net.HttpMethods.POST) + .url("http://hydra:6112/create-session") + .jsonContent(new Session.Builder() {{ + name = "test game"; + password = "1111"; + desc = "test desc"; + }}) + .build(); + Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() { + @Override + public void handleHttpResponse(Net.HttpResponse httpResponse) { + Session session = new Json().fromJson(Session.class, httpResponse.getResultAsString()); + Gdx.app.log(TAG, httpResponse.getResultAsString()); + Socket socket = null; + try { + socket = Gdx.net.newClientSocket(Net.Protocol.TCP, session.host, session.port, null); + } finally { + if (socket != null) socket.dispose(); + } + + done.set(true); + } + + @Override + public void failed(Throwable t) { + Gdx.app.log(TAG, ObjectUtils.toString(t)); + done.set(true); + } + + @Override + public void cancelled() { + Gdx.app.log(TAG, "cancelled"); + done.set(true); + } + }); + while (!done.get()); + } + + @Test + public void testDisconnect() { + final AtomicBoolean done = new AtomicBoolean(); + Net.HttpRequest request = new HttpRequestBuilder() + .newRequest() + .method(Net.HttpMethods.POST) + .url("http://hydra:6112/create-session") + .jsonContent(new Session.Builder() {{ + name = "test game"; + password = "1111"; + desc = "test desc"; + }}) + .build(); + Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() { + @Override + public void handleHttpResponse(Net.HttpResponse httpResponse) { + Session session = new Json().fromJson(Session.class, httpResponse.getResultAsString()); + Socket socket = null; + try { + socket = Gdx.net.newClientSocket(Net.Protocol.TCP, session.host, session.port, null); + Gdx.app.log(TAG, "Connected: " + socket.isConnected()); + new PrintWriter(socket.getOutputStream(), true).println("test"); + try { + Thread.sleep(500); + } catch (InterruptedException ignored) {} + } finally { + Gdx.app.log(TAG, "disconnecting..."); + if (socket != null) { + //IOUtils.closeQuietly(socket.getInputStream()); + IOUtils.closeQuietly(socket.getOutputStream()); + socket.dispose(); + } + } + + try { + Thread.sleep(500); + } catch (InterruptedException ignored) {} + + done.set(true); + } + + @Override + public void failed(Throwable t) { + Gdx.app.log(TAG, ObjectUtils.toString(t)); + done.set(true); + } + + @Override + public void cancelled() { + Gdx.app.log(TAG, "cancelled"); + done.set(true); + } + }); + while (!done.get()) ; + } } \ No newline at end of file