Added basic server support (create/join games and chat)

Separated services into ChatServer, DedicatedServer and Sessions
Single player uses same code-base as DedicatedServer with a special loopback socket
Added SelectCharacterScreen2 copy -- will replace later on with more versatile SelectCharacterScreen impl
This commit is contained in:
Collin Smith 2019-02-03 02:47:08 -08:00
parent a1e735031f
commit 49f1058566
16 changed files with 719 additions and 80 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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<AssetDescriptor> getDependencies() {
Array<AssetDescriptor> 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);

View File

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

View File

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

View File

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

View File

@ -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<DC6> characterselectscreenEXPDescriptor = new AssetDescriptor<>("data\\global\\ui\\CharSelect\\charselectbckg.dc6", DC6.class, DC6Loader.DC6Parameters.COMBINE);
TextureRegion characterselectscreenEXP;
final AssetDescriptor<DC6> MediumButtonBlankDescriptor = new AssetDescriptor<>("data\\global\\ui\\FrontEnd\\MediumButtonBlank.dc6", DC6.class);
final AssetDescriptor<DC6> 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<SelectButton> 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();
}
}

View File

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

View File

@ -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<Client> 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<Socket, BufferedReader, PrintWriter> 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<Socket, BufferedReader, PrintWriter> 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();
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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