mirror of
https://github.com/Anuken/Mindustry.git
synced 2025-07-04 15:27:19 +07:00
Implemented standalone server, fixed breaking bugs
This commit is contained in:
@ -22,7 +22,7 @@ allprojects {
|
||||
appName = "Mindustry"
|
||||
gdxVersion = '1.9.8'
|
||||
aiVersion = '1.8.1'
|
||||
uCoreVersion = 'ab9f87c'
|
||||
uCoreVersion = '09bbb56'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@ -120,6 +120,7 @@ project(":server") {
|
||||
compile project(":core")
|
||||
compile project(":kryonet")
|
||||
compile "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion"
|
||||
compile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
package io.anuke.mindustry;
|
||||
|
||||
import io.anuke.mindustry.core.*;
|
||||
import io.anuke.mindustry.io.BundleLoader;
|
||||
import io.anuke.mindustry.io.BlockLoader;
|
||||
import io.anuke.mindustry.io.BundleLoader;
|
||||
import io.anuke.ucore.core.Inputs;
|
||||
import io.anuke.ucore.modules.ModuleCore;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
@ -23,4 +24,11 @@ public class Mindustry extends ModuleCore {
|
||||
module(netClient = new NetClient());
|
||||
module(netCommon = new NetCommon());
|
||||
}
|
||||
|
||||
//hack
|
||||
@Override
|
||||
public void render() {
|
||||
super.render();
|
||||
Inputs.update();
|
||||
}
|
||||
}
|
||||
|
25
core/src/io/anuke/mindustry/MindustryServer.java
Normal file
25
core/src/io/anuke/mindustry/MindustryServer.java
Normal file
@ -0,0 +1,25 @@
|
||||
package io.anuke.mindustry;
|
||||
|
||||
import io.anuke.mindustry.core.*;
|
||||
import io.anuke.mindustry.io.BlockLoader;
|
||||
import io.anuke.mindustry.io.BundleLoader;
|
||||
import io.anuke.ucore.modules.ModuleCore;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
|
||||
public class MindustryServer extends ModuleCore {
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
headless = true;
|
||||
|
||||
BundleLoader.load();
|
||||
BlockLoader.load();
|
||||
|
||||
module(logic = new Logic());
|
||||
module(world = new World());
|
||||
module(netServer = new NetServer());
|
||||
module(netCommon = new NetCommon());
|
||||
module(serverControl = new ServerControl());
|
||||
}
|
||||
}
|
@ -62,6 +62,8 @@ public class Vars{
|
||||
//whether to hide ui, only on debug
|
||||
public static boolean showUI = true;
|
||||
|
||||
public static boolean headless = false;
|
||||
|
||||
public static float controllerMin = 0.25f;
|
||||
|
||||
public static float baseControllerSpeed = 11f;
|
||||
@ -89,6 +91,7 @@ public class Vars{
|
||||
public static NetCommon netCommon;
|
||||
public static NetServer netServer;
|
||||
public static NetClient netClient;
|
||||
public static ServerControl serverControl;
|
||||
|
||||
public static Player player;
|
||||
|
||||
|
@ -137,8 +137,7 @@ public class Control extends Module{
|
||||
Events.on(PlayEvent.class, () -> {
|
||||
renderer.clearTiles();
|
||||
|
||||
player.x = world.getCore().worldx();
|
||||
player.y = world.getCore().worldy() - tilesize*2;
|
||||
player.set(world.getSpawnX(), world.getSpawnY());
|
||||
|
||||
Core.camera.position.set(player.x, player.y, 0);
|
||||
|
||||
@ -263,7 +262,6 @@ public class Control extends Module{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
Inputs.update();
|
||||
|
||||
if(Gdx.input != proxy){
|
||||
Gdx.input = proxy;
|
||||
@ -352,5 +350,6 @@ public class Control extends Module{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ public class NetClient extends Module {
|
||||
public void update(){
|
||||
if(!Net.client()) return;
|
||||
|
||||
if(!state.is(State.menu) && Net.active()){
|
||||
if(!state.is(State.menu)){
|
||||
if(gotData) sync();
|
||||
}else if(!connecting){
|
||||
Net.disconnect();
|
||||
|
@ -22,29 +22,29 @@ public class NetCommon extends Module {
|
||||
|
||||
public NetCommon(){
|
||||
|
||||
Net.handleServer(ShootPacket.class, (id, packet) -> {
|
||||
Net.handle(ShootPacket.class, (packet) -> {
|
||||
Player player = playerGroup.getByID(packet.playerid);
|
||||
|
||||
Weapon weapon = (Weapon) Upgrade.getByID(packet.weaponid);
|
||||
weapon.shoot(player, packet.x, packet.y, packet.rotation);
|
||||
});
|
||||
|
||||
Net.handleServer(PlacePacket.class, (id, packet) -> {
|
||||
Net.handle(PlacePacket.class, (packet) -> {
|
||||
control.input().placeBlockInternal(packet.x, packet.y, Block.getByID(packet.block), packet.rotation, true, false);
|
||||
|
||||
Recipe recipe = Recipes.getByResult(Block.getByID(packet.block));
|
||||
if (recipe != null) state.inventory.removeItems(recipe.requirements);
|
||||
});
|
||||
|
||||
Net.handleServer(BreakPacket.class, (id, packet) -> {
|
||||
Net.handle(BreakPacket.class, (packet) -> {
|
||||
control.input().breakBlockInternal(packet.x, packet.y, false);
|
||||
});
|
||||
|
||||
Net.handleServer(ChatPacket.class, (id, packet) -> {
|
||||
Net.handle(ChatPacket.class, (packet) -> {
|
||||
ui.chatfrag.addMessage(packet.text, colorizeName(packet.id, packet.name));
|
||||
});
|
||||
|
||||
Net.handleServer(WeaponSwitchPacket.class, (id, packet) -> {
|
||||
Net.handle(WeaponSwitchPacket.class, (packet) -> {
|
||||
Player player = playerGroup.getByID(packet.playerid);
|
||||
|
||||
if (player == null) return;
|
||||
@ -53,17 +53,17 @@ public class NetCommon extends Module {
|
||||
player.weaponRight = (Weapon) Upgrade.getByID(packet.right);
|
||||
});
|
||||
|
||||
Net.handleServer(BlockTapPacket.class, (id, packet) -> {
|
||||
Net.handle(BlockTapPacket.class, (packet) -> {
|
||||
Tile tile = world.tile(packet.position);
|
||||
tile.block().tapped(tile);
|
||||
});
|
||||
|
||||
Net.handleServer(BlockConfigPacket.class, (id, packet) -> {
|
||||
Net.handle(BlockConfigPacket.class, (packet) -> {
|
||||
Tile tile = world.tile(packet.position);
|
||||
if (tile != null) tile.block().configure(tile, packet.data);
|
||||
});
|
||||
|
||||
Net.handleServer(PlayerDeathPacket.class, (id, packet) -> {
|
||||
Net.handle(PlayerDeathPacket.class, (packet) -> {
|
||||
Player player = playerGroup.getByID(packet.id);
|
||||
if(player == null) return;
|
||||
|
||||
@ -76,7 +76,7 @@ public class NetCommon extends Module {
|
||||
packet.name = null;
|
||||
packet.text = message;
|
||||
Net.send(packet, SendMode.tcp);
|
||||
ui.chatfrag.addMessage(message, null);
|
||||
if(!headless) ui.chatfrag.addMessage(message, null);
|
||||
}
|
||||
|
||||
public String colorizeName(int id, String name){
|
||||
|
@ -208,7 +208,7 @@ public class NetServer extends Module{
|
||||
}
|
||||
|
||||
public void update(){
|
||||
if(!closing && Net.active() && state.is(State.menu)){
|
||||
if(!closing && Net.server() && state.is(State.menu)){
|
||||
closing = true;
|
||||
weapons.clear();
|
||||
ui.loadfrag.show("$text.server.closing");
|
||||
|
138
core/src/io/anuke/mindustry/core/ServerControl.java
Normal file
138
core/src/io/anuke/mindustry/core/ServerControl.java
Normal file
@ -0,0 +1,138 @@
|
||||
package io.anuke.mindustry.core;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import io.anuke.mindustry.core.GameState.State;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
import io.anuke.mindustry.net.Packets.ChatPacket;
|
||||
import io.anuke.mindustry.world.Map;
|
||||
import io.anuke.ucore.UCore;
|
||||
import io.anuke.ucore.core.Effects;
|
||||
import io.anuke.ucore.core.Sounds;
|
||||
import io.anuke.ucore.modules.Module;
|
||||
import io.anuke.ucore.util.ColorCodes;
|
||||
import io.anuke.ucore.util.CommandHandler;
|
||||
import io.anuke.ucore.util.CommandHandler.Command;
|
||||
import io.anuke.ucore.util.CommandHandler.Response;
|
||||
import io.anuke.ucore.util.CommandHandler.ResponseType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static io.anuke.mindustry.Vars.*;
|
||||
import static io.anuke.ucore.util.ColorCodes.*;
|
||||
|
||||
public class ServerControl extends Module {
|
||||
private final CommandHandler handler = new CommandHandler("");
|
||||
|
||||
public ServerControl(){
|
||||
Effects.setScreenShakeProvider((a, b) -> {});
|
||||
Effects.setEffectProvider((a, b, c, d, e) -> {});
|
||||
Sounds.setHeadless(true);
|
||||
|
||||
//override default handling
|
||||
Net.handle(ChatPacket.class, (packet) -> {
|
||||
info("&y" + (packet.name == null ? "" : packet.name) + ": &lb{0}", packet.text);
|
||||
});
|
||||
|
||||
registerCommands();
|
||||
Thread thread = new Thread(this::readCommands, "Server Controls");
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
|
||||
info("&lcServer loaded. Type &ly'help'&lc for help.");
|
||||
}
|
||||
|
||||
private void registerCommands(){
|
||||
handler.register("help", "", "Displays this command list.", arg -> {
|
||||
info("Commands:");
|
||||
for(Command command : handler.getCommandList()){
|
||||
print(" &y" + command.text + (command.params.isEmpty() ? "" : " ") + command.params + " - &lm" + command.description);
|
||||
}
|
||||
});
|
||||
|
||||
handler.register("exit", "", "Exit the server application.", arg -> {
|
||||
info("Shutting down server.");
|
||||
Net.dispose();
|
||||
Gdx.app.exit();
|
||||
});
|
||||
|
||||
handler.register("stop", "", "Stop hosting the server.", arg -> {
|
||||
Net.closeServer();
|
||||
state.set(State.menu);
|
||||
});
|
||||
|
||||
handler.register("host", "<mapname>", "Open the server with a specific map.", arg -> {
|
||||
if(state.is(State.playing)){
|
||||
err("Already hosting. Type 'stop' to stop hosting first.");
|
||||
}
|
||||
|
||||
String search = arg[0];
|
||||
Map result = null;
|
||||
for(Map map : world.maps().list()){
|
||||
if(map.name.equalsIgnoreCase(search))
|
||||
result = map;
|
||||
}
|
||||
|
||||
if(result == null){
|
||||
err("No map with name &y'{0}'&lg found.", search);
|
||||
return;
|
||||
}
|
||||
|
||||
info("Loading map...");
|
||||
logic.reset();
|
||||
world.loadMap(result);
|
||||
state.set(State.playing);
|
||||
info("Map loaded.");
|
||||
|
||||
try {
|
||||
Net.host(port);
|
||||
info("Server opened.");
|
||||
}catch (IOException e){
|
||||
UCore.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void readCommands(){
|
||||
Scanner scan = new Scanner(System.in);
|
||||
System.out.print(LIGHT_BLUE + "> " + RESET);
|
||||
while(true){
|
||||
String line = scan.nextLine();
|
||||
|
||||
Gdx.app.postRunnable(() -> {
|
||||
Response response = handler.handleMessage(line);
|
||||
|
||||
if (response.type == ResponseType.unknownCommand) {
|
||||
err("Invalid command. Type 'help' for help.");
|
||||
} else if (response.type == ResponseType.invalidArguments) {
|
||||
err("Invalid command arguments. Usage: " + response.command.text + " " + response.command.params);
|
||||
}
|
||||
|
||||
System.out.print(LIGHT_BLUE + "> " + RESET);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void print(String text, Object... args){
|
||||
System.out.println(format(text, args) + RESET);
|
||||
}
|
||||
|
||||
private void info(String text, Object... args){
|
||||
print(LIGHT_GREEN + BOLD + format(text, args));
|
||||
}
|
||||
|
||||
private void err(String text, Object... args){
|
||||
print(LIGHT_RED + BOLD + format(text, args));
|
||||
}
|
||||
|
||||
private String format(String text, Object... args){
|
||||
for(int i = 0; i < args.length; i ++){
|
||||
text = text.replace("{" + i + "}", args[i].toString());
|
||||
}
|
||||
|
||||
for(String color : ColorCodes.getColorCodes()){
|
||||
text = text.replace("&" + color, ColorCodes.getColorText(color));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ public class World extends Module{
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(!(Net.client()))
|
||||
if(!Net.client())
|
||||
pathfind.update();
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ public class World extends Module{
|
||||
}
|
||||
|
||||
public float getSpawnY(){
|
||||
return core.worldy() - tilesize/2;
|
||||
return core.worldy() - tilesize*2;
|
||||
}
|
||||
|
||||
public boolean solid(int x, int y){
|
||||
|
@ -147,7 +147,7 @@ public class Maps implements Disposable{
|
||||
if(arr != null){ //can be an empty map file
|
||||
for(Map map : arr){
|
||||
map.pixmap = new Pixmap(file.sibling(map.name + ".png"));
|
||||
map.texture = new Texture(map.pixmap);
|
||||
if(!headless) map.texture = new Texture(map.pixmap);
|
||||
maps.put(map.id, map);
|
||||
mapNames.put(map.name, map);
|
||||
lastID = Math.max(lastID, map.id);
|
||||
@ -164,7 +164,7 @@ public class Maps implements Disposable{
|
||||
@Override
|
||||
public void dispose(){
|
||||
for(Map map : maps.values()){
|
||||
map.texture.dispose();
|
||||
if(map.texture != null) map.texture.dispose();
|
||||
map.pixmap.dispose();
|
||||
}
|
||||
maps.clear();
|
||||
|
@ -15,6 +15,9 @@ import io.anuke.ucore.function.Consumer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.anuke.mindustry.Vars.headless;
|
||||
import static io.anuke.mindustry.Vars.ui;
|
||||
|
||||
public class Net{
|
||||
public static final int version = 13;
|
||||
|
||||
@ -29,6 +32,15 @@ public class Net{
|
||||
|
||||
private static IntMap<StreamBuilder> streams = new IntMap<>();
|
||||
|
||||
/**Display a network error.*/
|
||||
public static void showError(String text){
|
||||
if(!headless){
|
||||
ui.showError(text);
|
||||
}else{
|
||||
UCore.log(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**Sets the client loaded status, or whether it will recieve normal packets from the server.*/
|
||||
public static void setClientLoaded(boolean loaded){
|
||||
clientLoaded = loaded;
|
||||
|
@ -266,7 +266,7 @@ public class NetworkIO {
|
||||
world.loadMap(world.maps().getMap(mapid), seed);
|
||||
renderer.clearTiles();
|
||||
|
||||
player.set(world.getCore().worldx(), world.getCore().worldy());
|
||||
player.set(world.getSpawnX(), world.getSpawnY());
|
||||
|
||||
for(int x = 0; x < world.width(); x ++){
|
||||
for(int y = 0; y < world.height(); y ++){
|
||||
|
@ -79,7 +79,7 @@ public class KryoClient implements ClientProvider{
|
||||
Net.handleClientReceived(object);
|
||||
}catch (Exception e){
|
||||
if(e instanceof KryoNetException && e.getMessage() != null && e.getMessage().toLowerCase().contains("incorrect")) {
|
||||
ui.showError("$text.server.mismatch");
|
||||
Net.showError("$text.server.mismatch");
|
||||
netClient.disconnectQuietly();
|
||||
}else{
|
||||
throw new RuntimeException(e);
|
||||
@ -207,7 +207,7 @@ public class KryoClient implements ClientProvider{
|
||||
private void handleException(Exception e){
|
||||
e.printStackTrace();
|
||||
if(e instanceof KryoNetException){
|
||||
Gdx.app.postRunnable(() -> ui.showError("$text.server.mismatch"));
|
||||
Gdx.app.postRunnable(() -> Net.showError("$text.server.mismatch"));
|
||||
}else{
|
||||
//TODO better exception handling.
|
||||
disconnect();
|
||||
|
@ -18,9 +18,11 @@ public class KryoRegistrator {
|
||||
}
|
||||
|
||||
public static ByteBuffer writeServerData(){
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1 + Vars.player.name.length() + 4);
|
||||
buffer.put((byte)Vars.player.name.length());
|
||||
buffer.put(Vars.player.name.getBytes());
|
||||
String host = Vars.headless ? "Server" : Vars.player.name;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1 + host.getBytes().length + 4);
|
||||
buffer.put((byte)host.getBytes().length);
|
||||
buffer.put(host.getBytes());
|
||||
buffer.putInt(Net.getConnections().size + 1);
|
||||
return buffer;
|
||||
}
|
||||
|
@ -35,8 +35,6 @@ import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import static io.anuke.mindustry.Vars.ui;
|
||||
|
||||
public class KryoServer implements ServerProvider {
|
||||
final boolean debug = false;
|
||||
final Server server;
|
||||
@ -434,10 +432,10 @@ public class KryoServer implements ServerProvider {
|
||||
ex.printStackTrace();
|
||||
if(ex instanceof BindException){
|
||||
Net.closeServer();
|
||||
ui.showError("$text.server.addressinuse");
|
||||
Net.showError("$text.server.addressinuse");
|
||||
}else if(ex.getMessage().equals("Permission denied")){
|
||||
Net.closeServer();
|
||||
ui.showError("Permission denied.");
|
||||
Net.showError("Permission denied.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ package io.anuke.mindustry.server;
|
||||
import com.badlogic.gdx.backends.headless.HeadlessApplication;
|
||||
import io.anuke.kryonet.KryoClient;
|
||||
import io.anuke.kryonet.KryoServer;
|
||||
import io.anuke.mindustry.Mindustry;
|
||||
import io.anuke.mindustry.MindustryServer;
|
||||
import io.anuke.mindustry.net.Net;
|
||||
|
||||
public class ServerLauncher{
|
||||
@ -13,6 +13,6 @@ public class ServerLauncher{
|
||||
Net.setClientProvider(new KryoClient());
|
||||
Net.setServerProvider(new KryoServer());
|
||||
|
||||
new HeadlessApplication(new Mindustry());
|
||||
new HeadlessApplication(new MindustryServer());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user