Added server browser chat

Some work on in-game chat
Separated Server into multiple systems (will be easier to transition to distributed servers later)
Minor entity changes -- may transition to an ECS soon
This commit is contained in:
Collin Smith 2019-01-30 15:40:17 -08:00
parent cb11853a26
commit a1e735031f
18 changed files with 640 additions and 166 deletions

View File

@ -41,5 +41,6 @@ public class Keys {
public static final MappedKey Character = new MappedKey("Character", "character", Input.Keys.C, Input.Keys.A);
public static final MappedKey Stash = new MappedKey("Stash", "stash", Input.Keys.NUMPAD_1);
public static final MappedKey SwapWeapons = new MappedKey("SwapWeapons", "swap", Input.Keys.W);
public static final MappedKey Enter = new MappedKey("Enter", "enter", Input.Keys.ENTER);
}

View File

@ -7,9 +7,11 @@ import com.badlogic.gdx.utils.Disposable;
public class Textures implements Disposable {
public final Texture white;
public final Texture modal;
public Textures() {
white = createTexture(Diablo.colors.white);
modal = createTexture(new Color(0.0f, 0.0f, 0.0f, 0.5f));
}
public Texture createTexture(Color color) {
@ -24,5 +26,6 @@ public class Textures implements Disposable {
@Override
public void dispose() {
white.dispose();
modal.dispose();
}
}

View File

@ -72,7 +72,12 @@ public class Direction {
private Direction() {}
public static int radiansToDirection(float radians, int directions) {
return radiansToDirection16(radians);
switch (directions) {
case 1: return 0;
case 8: return radiansToDirection8(radians);
case 16: return radiansToDirection16(radians);
default: return 0;
}
}
static final int DIRS_16[] = {7, 13, 2, 12, 6, 11, 1, 10, 5, 9, 0, 8, 4, 15, 3, 14};

View File

@ -23,6 +23,7 @@ import gdx.diablo.codec.DCC;
import gdx.diablo.entity.Direction;
import gdx.diablo.graphics.PaletteIndexedBatch;
import gdx.diablo.item.Item;
import gdx.diablo.map.DS1;
import gdx.diablo.map.DT1.Tile;
public class Entity {
@ -121,6 +122,18 @@ public class Entity {
invalidate();
}
public static Entity create(DS1 ds1, DS1.Object obj) {
final int type = obj.type;
switch (type) {
case DS1.Object.DYNAMIC_TYPE:
throw new UnsupportedOperationException("Unsupported type: " + type);
case DS1.Object.STATIC_TYPE:
return null;
default:
throw new AssertionError("Unsupported type: " + type);
}
}
public void setMode(String mode) {
setMode(mode, mode);
}
@ -219,17 +232,42 @@ public class Entity {
Gdx.app.log(TAG, path);
if (DEBUG_ASSETS) {
AssetDescriptor<DCC> descriptor = new AssetDescriptor<>(path, DCC.class);
final AssetDescriptor<DCC> descriptor = new AssetDescriptor<>(path, DCC.class);
Diablo.assets.load(descriptor);
Diablo.assets.finishLoadingAsset(descriptor);
DCC dcc = Diablo.assets.get(descriptor);
animation.setLayer(c, dcc);
/*Runnable loader = new Runnable() {
@Override
public void run() {
if (!Diablo.assets.isLoaded(descriptor)) {
Gdx.app.postRunnable(this);
return;
}
DCC dcc = Diablo.assets.get(descriptor);
animation.setLayer(c, dcc);
Item item = getItem(comp);
if (item != null) {
animation.getLayer(c).setTransform(item.charColormap, item.charColorIndex);
}
}
};*/
//Gdx.app.postRunnable(loader);
}
Item item = getItem(comp);
if (item != null) {
animation.getLayer(layer.component).setTransform(item.charColormap, item.charColorIndex);
}
//if (BodyLoc.TORS.contains(c)) {
Item item = getItem(comp);
if (item != null) {
// FIXME: colors don't look right for sorc Tirant circlet changing hair color
// putting a ruby in a white circlet not change color on item or character
// circlets and other items with hidden magic level might work different?
animation.getLayer(layer.component).setTransform(item.charColormap, item.charColorIndex);
//System.out.println(item.getName() + ": " + item.charColormap + " ; " + item.charColorIndex);
}
//}
}
dirty = 0;
@ -272,7 +310,8 @@ public class Entity {
}
public int getDirection() {
return Direction.radiansToDirection(angle, 16);
int numDirs = animation.getNumDirections();
return Direction.radiansToDirection(angle, numDirs);
}
public GridPoint2 origin() {

View File

@ -71,7 +71,7 @@ public class Player extends Entity {
this.stats = new Stats();
equipped.putAll(d2s.items.equipped);
inventory.addAll(d2s.items.inventory);
setMode("TN");
for (Map.Entry<BodyLoc, Item> entry : equipped.entrySet()) {
entry.getValue().load();

View File

@ -53,4 +53,8 @@ public enum BodyLoc {
public int components() {
return components;
}
public boolean contains(int component) {
return components != 0 && (components & (1 << component)) != 0;
}
}

View File

@ -68,6 +68,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
OrthographicCamera camera;
InputProcessor inputProcessorTest;
//TextArea input;
//Char character;
public Player player;
@ -85,6 +86,19 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
public GameScreen(final Player player) {
this.player = player;
/*
input = new TextArea("", new TextArea.TextFieldStyle() {{
this.font = Diablo.fonts.fontformal12;
this.fontColor = Diablo.colors.white;
this.background = new TextureRegionDrawable(Diablo.textures.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);
*/
escapePanel = new EscapePanel();
controlPanel = new ControlPanel(this);
@ -116,12 +130,14 @@ 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(controlPanel);
stage.addActor(escapePanel);
stage.addActor(inventoryPanel);
stage.addActor(characterPanel);
stage.addActor(stashPanel);
controlPanel.toFront();
//input.toFront();
escapePanel.toFront();
if (Gdx.app.getType() == Application.ApplicationType.Android || DEBUG_TOUCHPAD) {
@ -141,7 +157,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
public void changed(ChangeEvent event, Actor actor) {
float x = touchpad.getKnobPercentX();
float y = touchpad.getKnobPercentY();
if (x == 0 && y == 0) {
if (x == 0 && y == 0 || UIUtils.shift()) {
player.setMode("TN");
return;
//} else if (-0.5f < x && x < 0.5f
@ -171,6 +187,20 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
} else {
escapePanel.setVisible(true);
}
/*} else if (key == Keys.Enter) {
boolean visible = !input.isVisible();
if (!visible) {
String text = input.getText();
if (!text.isEmpty()) {
Gdx.app.debug(TAG, text);
input.setText("");
}
}
input.setVisible(visible);
if (visible) {
stage.setKeyboardFocus(input);
}*/
} else if (key == Keys.Inventory) {
inventoryPanel.setVisible(!inventoryPanel.isVisible());
} else if (key == Keys.Character) {
@ -292,12 +322,14 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
Keys.Character.addStateListener(mappedKeyStateListener);
Keys.Stash.addStateListener(mappedKeyStateListener);
Keys.SwapWeapons.addStateListener(mappedKeyStateListener);
Keys.Enter.addStateListener(mappedKeyStateListener);
Diablo.input.addProcessor(stage);
Diablo.input.addProcessor(inputProcessorTest);
updateTask = Timer.schedule(new Timer.Task() {
@Override
public void run() {
if (UIUtils.shift()) return;
player.move();
mapRenderer.setPosition(player.origin());
}
@ -311,6 +343,7 @@ public class GameScreen extends ScreenAdapter implements LoadingScreen.Loadable
Keys.Character.removeStateListener(mappedKeyStateListener);
Keys.Stash.removeStateListener(mappedKeyStateListener);
Keys.SwapWeapons.removeStateListener(mappedKeyStateListener);
Keys.Enter.removeStateListener(mappedKeyStateListener);
Diablo.input.removeProcessor(stage);
Diablo.input.removeProcessor(inputProcessorTest);

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.ScreenAdapter;
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.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.Group;
@ -24,7 +25,14 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.SerializationException;
import org.apache.commons.io.IOUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
@ -34,8 +42,10 @@ import gdx.diablo.graphics.PaletteIndexedBatch;
import gdx.diablo.loader.DC6Loader;
import gdx.diablo.server.Account;
import gdx.diablo.server.Session;
import gdx.diablo.server.SessionError;
import gdx.diablo.util.EventUtils;
import gdx.diablo.widget.Label;
import gdx.diablo.widget.TextArea;
import gdx.diablo.widget.TextButton;
import gdx.diablo.widget.TextField;
@ -70,6 +80,13 @@ public class LobbyScreen extends ScreenAdapter {
private Stage stage;
private TextArea taChatOutput;
private TextField tfChatInput;
private Socket socket;
private PrintWriter out;
private BufferedReader in;
public LobbyScreen(Account account) {
Diablo.assets.load(waitingroombkgdDescriptor);
Diablo.assets.load(blankbckgDescriptor);
@ -262,7 +279,37 @@ public class LobbyScreen extends ScreenAdapter {
desc = tfDesc.getText();
}})
.build();
Gdx.net.sendHttpRequest(request, null);
Gdx.net.sendHttpRequest(request, new Net.HttpResponseListener() {
@Override
public void handleHttpResponse(Net.HttpResponse httpResponse) {
String response = httpResponse.getResultAsString();
try {
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();
}
} catch (SerializationException e) {
SessionError error = new Json().fromJson(SessionError.class, response);
Gdx.app.log(TAG, "create-session " + error.toString());
}
}
@Override
public void failed(Throwable t) {
Gdx.app.log(TAG, "create-session " + t.getMessage());
}
@Override
public void cancelled() {
Gdx.app.log(TAG, "create-session " + "cancelled");
}
});
}
});
tfGameName.addListener(new ChangeListener() {
@ -420,12 +467,54 @@ public class LobbyScreen extends ScreenAdapter {
}};
stage.addActor(right);
final Table left = new Table() {{
setSize(350, 332);
setPosition(55, 115);
add(taChatOutput = new TextArea("", new TextArea.TextFieldStyle() {{
font = Diablo.fonts.fontformal10;
fontColor = Diablo.colors.white;
cursor = new TextureRegionDrawable(Diablo.textures.white);
}}) {{
setDisabled(true);
}}).grow().row();
add(tfChatInput = new TextField("", textFieldStyle) {{
setTextFieldListener(new TextFieldListener() {
@Override
public void keyTyped(com.badlogic.gdx.scenes.scene2d.ui.TextField textField, char c) {
if (c == '\r' || c == '\n') {
if (socket != null && socket.isConnected()) {
out.println(textField.getText());
textField.setText("");
}
}
}
});
}}).growX();
}};
stage.addActor(left);
stage.setKeyboardFocus(tfChatInput);
Diablo.input.addProcessor(stage);
connect();
}
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");
}
}
@Override
public void hide() {
Diablo.input.removeProcessor(stage);
IOUtils.closeQuietly(out);
if (socket != null) socket.dispose();
}
@Override
@ -442,6 +531,17 @@ public class LobbyScreen extends ScreenAdapter {
@Override
public void render(float delta) {
if (in != null) {
try {
for (String str; in.ready() && (str = in.readLine()) != null;) {
taChatOutput.appendText(str);
taChatOutput.appendText("\n");
}
} catch (IOException e) {
Gdx.app.error(TAG, e.getMessage());
}
}
PaletteIndexedBatch b = Diablo.batch;
b.begin(Diablo.palettes.act1);
b.draw(waitingroombkgd, Diablo.VIRTUAL_WIDTH_CENTER - (waitingroombkgd.getRegionWidth() / 2), Diablo.VIRTUAL_HEIGHT - waitingroombkgd.getRegionHeight() + 72);

View File

@ -0,0 +1,10 @@
package gdx.diablo.server;
public class Message {
public String from;
public String text;
private Message() {}
}

View File

@ -2,9 +2,11 @@ package gdx.diablo.server;
public class Session {
private String name;
private String password;
private String desc;
public String name;
public String password;
public String desc;
public String host;
public int port;
private Session() {}
@ -18,9 +20,13 @@ public class Session {
desc = builder.desc;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name;
return getName();
}
public static class Builder {

View File

@ -0,0 +1,27 @@
package gdx.diablo.server;
public class SessionError {
private int code;
private String message;
private SessionError() {}
public SessionError(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public String toString() {
return code + " " + message;
}
}

View File

@ -0,0 +1,26 @@
package gdx.diablo.widget;
import com.badlogic.gdx.utils.Disposable;
public class TextArea extends com.badlogic.gdx.scenes.scene2d.ui.TextArea implements Disposable {
public TextArea(TextFieldStyle style) {
this("", style);
}
public TextArea(String text, TextFieldStyle style) {
super(text, style);
}
@Override
public void dispose() {
}
public static class TextFieldStyle extends com.badlogic.gdx.scenes.scene2d.ui.TextArea.TextFieldStyle {
public TextFieldStyle() {
}
}
}

View File

@ -17,7 +17,11 @@ import gdx.diablo.COFs;
import gdx.diablo.Diablo;
import gdx.diablo.Files;
import gdx.diablo.codec.D2S;
import gdx.diablo.codec.DC6;
import gdx.diablo.codec.DCC;
import gdx.diablo.codec.StringTBLs;
import gdx.diablo.loader.DC6Loader;
import gdx.diablo.loader.DCCLoader;
import gdx.diablo.mpq.MPQFileHandleResolver;
public class EntityTest {
@ -33,7 +37,12 @@ public class EntityTest {
resolver.add(Gdx.files.absolute("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Diablo II\\patch_d2.mpq"));
resolver.add(Gdx.files.absolute("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Diablo II\\d2exp.mpq"));
resolver.add(Gdx.files.absolute("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Diablo II\\d2data.mpq"));
Diablo.assets = new AssetManager(resolver);
resolver.add(Gdx.files.absolute("C:\\Program Files (x86)\\Steam\\steamapps\\common\\Diablo II\\d2char.mpq"));
Diablo.assets = new AssetManager();
Diablo.assets.setLoader(DCC.class, new DCCLoader(Diablo.mpqs));
Diablo.assets.setLoader(DC6.class, new DC6Loader(Diablo.mpqs));
Diablo.cofs = new COFs(Diablo.assets);
Diablo.files = new Files(Diablo.assets);
Diablo.string = new StringTBLs(resolver);

View File

@ -4,7 +4,7 @@ sourceCompatibility = 1.7
sourceSets.main.java.srcDirs = [ "src/" ]
sourceSets.test.java.srcDirs = [ "test/" ]
project.ext.mainClassName = "gdx.diablo.server.Server"
project.ext.mainClassName = "gdx.diablo.server.ServerBrowser"
project.ext.assetsDir = new File("../android/assets");
task run(dependsOn: classes, type: JavaExec) {

View File

@ -0,0 +1,131 @@
package gdx.diablo.server;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.Socket;
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.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
public class ChatServer extends ApplicationAdapter {
private static final String TAG = "ChatServer";
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new ChatServer(), config);
}
private final Json json = new Json();
private Set<PrintWriter> clients = new CopyOnWriteArraySet<>();
ThreadGroup clientThreads;
ServerSocket server;
Thread thread;
AtomicBoolean kill;
ChatServer() {}
@Override
public void create() {
final Calendar calendar = Calendar.getInstance();
DateFormat format = DateFormat.getDateTimeInstance();
Gdx.app.log(TAG, format.format(calendar.getTime()));
try {
InetAddress address = InetAddress.getLocalHost();
Gdx.app.log(TAG, "IP Address: " + address.getHostAddress());
Gdx.app.log(TAG, "Host Name: " + address.getHostName());
} catch (UnknownHostException e) {
Gdx.app.error(TAG, e.getMessage(), e);
}
clientThreads = new ThreadGroup("Clients");
kill = new AtomicBoolean(false);
server = Gdx.net.newServerSocket(Net.Protocol.TCP, 6113, null);
thread = new Thread(new Runnable() {
@Override
public void run() {
while (!kill.get()) {
Socket socket = server.accept(null);
Gdx.app.log(TAG, "CONNECT " + socket.getRemoteAddress());
new Client(socket).start();
}
}
});
thread.setName("ChatServer");
thread.start();
}
@Override
public void render() {
}
@Override
public void dispose() {
Gdx.app.log(TAG, "shutting down...");
kill.set(true);
try {
thread.join();
} catch (Throwable ignored) {}
server.dispose();
}
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)));
this.socket = socket;
}
@Override
public void run() {
try {
in = IOUtils.buffer(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
clients.add(out);
for (String input; (input = in.readLine()) != null; ) {
String message = "MESSAGE " + socket.getRemoteAddress() + ": " + input;
Gdx.app.log(TAG, message);
for (PrintWriter client : clients) {
client.println(message);
}
}
} catch (Throwable t) {
Gdx.app.log(TAG, "ERROR " + socket.getRemoteAddress() + ": " + t.getMessage());
} finally {
String message = "DISCONNECT " + socket.getRemoteAddress();
Gdx.app.log(TAG, message);
for (PrintWriter client : clients) {
client.println(message);
}
//IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
if (out != null) clients.remove(out);
if (socket != null) socket.dispose();
}
}
}
}

View File

@ -1,173 +1,39 @@
package gdx.diablo.server;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.Socket;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.concurrent.atomic.AtomicBoolean;
public class Server extends ApplicationAdapter {
public class Server extends Thread {
private static final String TAG = "Server";
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new Server(), config);
}
private final Json json = new Json();
private Array<Session> sessions = new Array<>(new Session[] {
new Session("Kmbaal-33"),
new Session("Cbaalz73"),
new Session("Killin Foos"),
new Session("Skulders 4 Scri"),
});
ServerSocket server;
Thread serverThread;
AtomicBoolean killServer;
AtomicBoolean kill;
int port;
Server() {}
@Override
public void create() {
Calendar calendar = Calendar.getInstance();
DateFormat format = DateFormat.getDateTimeInstance();
Gdx.app.log(TAG, format.format(calendar.getTime()));
try {
InetAddress address = InetAddress.getLocalHost();
Gdx.app.log(TAG, "IP Address: " + address.getHostAddress());
Gdx.app.log(TAG, "Host Name: " + address.getHostName());
} catch (UnknownHostException e) {
Gdx.app.error(TAG, e.getMessage(), e);
}
Gdx.app.log(TAG, "awaiting connection...");
server = Gdx.net.newServerSocket(Net.Protocol.TCP, 6112, null);
killServer = new AtomicBoolean(false);
serverThread = new Thread(new Runnable() {
@Override
public void run() {
while (!killServer.get()) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = server.accept(null);
Gdx.app.log(TAG, "connection from " + socket.getRemoteAddress());
in = IOUtils.buffer(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), false);
String statusLine = in.readLine();
Gdx.app.log(TAG, statusLine);
String[] parts = statusLine.split("\\s+", 3);
String path = parts[1];
if (path.equals("/get-sessions")) {
getSessions(out);
} else if (path.equals("/create-session")) {
createSession(in, out);
} else if (path.equals("/find-server")) {
findServer(out);
} else if (path.equals("/login")) {
login(in, out);
}
} catch (Throwable t) {
Gdx.app.error(TAG, t.getMessage(), t);
} finally {
IOUtils.closeQuietly(out);
//IOUtils.closeQuietly(in);
if (socket != null) socket.dispose();
}
}
}
});
serverThread.start();
Server(ThreadGroup group, String name, int port) {
super(group, name);
this.port = port;
kill = new AtomicBoolean(false);
}
@Override
public void dispose() {
Gdx.app.log(TAG, "shutting down...");
killServer.set(true);
try {
serverThread.join();
} catch (Throwable ignored) {}
server.dispose();
}
private void getSessions(PrintWriter out) {
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(sessions));
}
private void createSession(BufferedReader in, PrintWriter out) {
String content = getContent(in);
//System.out.println(content);
Session.Builder builder = json.fromJson(Session.Builder.class, content);
sessions.add(builder.build());
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(builder.build()));
}
private void findServer(PrintWriter out) {
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
}
private void login(BufferedReader in, PrintWriter out) {
String content = getContent(in);
//System.out.println(content);
Account.Builder builder = json.fromJson(Account.Builder.class, content);
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(builder.build()));
}
/**
* TODO: parse packet content-length and read that many chars into a string
*/
public String getContent(BufferedReader reader) {
try {
int length = -1;
for (String str; (str = reader.readLine()) != null && !str.isEmpty();) {
if (StringUtils.startsWithIgnoreCase(str, "Content-Length:")) {
str = StringUtils.replaceIgnoreCase(str, "Content-Length:", "").trim();
length = NumberUtils.toInt(str, length);
}
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();
}
//return reader.readLine();
char[] chars = new char[length];
reader.read(chars);
return new String(chars);
} catch (IOException e) {
Gdx.app.error(TAG, e.getMessage(), e);
return null;
}
}
}

View File

@ -0,0 +1,179 @@
package gdx.diablo.server;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Net;
import com.badlogic.gdx.backends.headless.HeadlessApplication;
import com.badlogic.gdx.backends.headless.HeadlessApplicationConfiguration;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.net.ServerSocket;
import com.badlogic.gdx.net.Socket;
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.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class ServerBrowser extends ApplicationAdapter {
private static final String TAG = "Server";
public static void main(String[] args) {
HeadlessApplicationConfiguration config = new HeadlessApplicationConfiguration();
new HeadlessApplication(new ServerBrowser(), config);
}
private final Json json = new Json();
private Map<String, Session> sessions = new ConcurrentHashMap<>();
ServerSocket server;
Thread thread;
AtomicBoolean kill;
ThreadGroup sessionGroup = new ThreadGroup("Sessions");
private Map<String, Thread> servers = new ConcurrentHashMap<>();
ServerBrowser() {}
@Override
public void create() {
final Calendar calendar = Calendar.getInstance();
DateFormat format = DateFormat.getDateTimeInstance();
Gdx.app.log(TAG, format.format(calendar.getTime()));
try {
InetAddress address = InetAddress.getLocalHost();
Gdx.app.log(TAG, "IP Address: " + address.getHostAddress());
Gdx.app.log(TAG, "Host Name: " + address.getHostName());
} catch (UnknownHostException e) {
Gdx.app.error(TAG, e.getMessage(), e);
}
kill = new AtomicBoolean(false);
server = Gdx.net.newServerSocket(Net.Protocol.TCP, 6112, null);
thread = new Thread(new Runnable() {
@Override
public void run() {
while (!kill.get()) {
Socket socket = null;
BufferedReader in = null;
PrintWriter out = null;
try {
socket = server.accept(null);
Gdx.app.log(TAG, "connection from " + socket.getRemoteAddress());
in = IOUtils.buffer(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), false);
String statusLine = in.readLine();
Gdx.app.log(TAG, statusLine);
String[] parts = statusLine.split("\\s+", 3);
String path = parts[1];
if (path.equals("/get-sessions")) {
getSessions(out);
} else if (path.equals("/create-session")) {
createSession(in, out);
} else if (path.equals("/find-server")) {
findServer(out);
} else if (path.equals("/login")) {
login(in, out);
} else if (path.equals("/chat")) {
chat(in, out);
}
} catch (Throwable t) {
Gdx.app.error(TAG, t.getMessage(), t);
} finally {
//IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
if (socket != null) socket.dispose();
}
}
}
});
thread.setName("ServerBrowser");
thread.start();
}
@Override
public void render() {
}
@Override
public void dispose() {
Gdx.app.log(TAG, "shutting down...");
kill.set(true);
try {
thread.join();
} catch (Throwable ignored) {}
server.dispose();
}
private void getSessions(PrintWriter out) {
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(sessions.values()));
}
private void createSession(BufferedReader in, PrintWriter out) {
String content = ServerUtils.getContent(in);
Session.Builder builder = json.fromJson(Session.Builder.class, content);
if (sessions.containsKey(builder.name)) {
SessionError error = new SessionError(5138, "A game already exists with that name");
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(error));
return;
} else if (sessions.size() >= 2) {
SessionError error = new SessionError(5140, "No game server available");
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(error));
return;
}
Session session = builder.build();
session.host = "hydra";
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);
server.start();
servers.put(session.getName(), server);
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(session));
}
private void findServer(PrintWriter out) {
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
}
private void login(BufferedReader in, PrintWriter out) {
String content = ServerUtils.getContent(in);
//System.out.println(content);
Account.Builder builder = json.fromJson(Account.Builder.class, content);
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
out.print(json.toJson(builder.build()));
}
private void chat(BufferedReader in, PrintWriter out) {
out.print("HTTP/1.1 200\r\n");
out.print("\r\n");
}
}

View File

@ -0,0 +1,35 @@
package gdx.diablo.server;
import com.badlogic.gdx.Gdx;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import java.io.BufferedReader;
import java.io.IOException;
public class ServerUtils {
private static final String TAG = "ServerUtils";
private ServerUtils() {}
public static String getContent(BufferedReader reader) {
try {
int length = -1;
for (String str; (str = reader.readLine()) != null && !str.isEmpty();) {
if (StringUtils.startsWithIgnoreCase(str, "Content-Length:")) {
str = StringUtils.replaceIgnoreCase(str, "Content-Length:", "").trim();
length = NumberUtils.toInt(str, length);
}
}
char[] chars = new char[length];
reader.read(chars);
return new String(chars);
} catch (IOException e) {
Gdx.app.error(TAG, e.getMessage(), e);
return null;
}
}
}