add server

This commit is contained in:
Sen 2025-05-01 15:13:38 +02:00
parent d5269922b9
commit 76ecfb39ab
25 changed files with 603 additions and 520 deletions

View file

@ -3,7 +3,6 @@ package game;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@ -32,6 +31,7 @@ import game.entity.npc.EntityHuman;
import game.entity.npc.EntityNPC;
import game.init.Config;
import game.init.EntityRegistry;
import game.init.Registry;
import game.init.UniverseRegistry;
import game.log.Log;
import game.nbt.NBTLoader;
@ -41,8 +41,7 @@ import game.network.IThreadListener;
import game.network.LazyLoadBase;
import game.network.NetConnection;
import game.network.NetHandler.ThreadQuickExitException;
import game.network.HandshakeHandlerMemory;
import game.network.HandshakeHandlerTCP;
import game.network.HandshakeHandler;
import game.network.Player;
import game.network.Packet;
import game.network.PacketDecoder;
@ -65,6 +64,7 @@ import game.packet.SPacketTimeUpdate;
import game.packet.SPacketWorld;
import game.potion.PotionEffect;
import game.util.ExtMath;
import game.util.Util;
import game.world.BlockPos;
import game.world.PortalType;
import game.world.Position;
@ -81,23 +81,20 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalServerChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
public final class Server implements Runnable, IThreadListener {
public final class Server implements IThreadListener {
private static final LazyLoadBase<NioEventLoopGroup> SERVER_NIO_EVENTLOOP = new LazyLoadBase<NioEventLoopGroup>() {
protected NioEventLoopGroup load() {
return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
}
};
private final Thread serverThread;
private final Thread serverThread = Thread.currentThread();
private final List<NetConnection> clients = Collections.<NetConnection>synchronizedList(Lists.<NetConnection>newArrayList());
private final List<Player> players = Lists.<Player>newArrayList();
private final Map<String, Player> usermap = Maps.<String, Player>newHashMap();
@ -109,17 +106,11 @@ public final class Server implements Runnable, IThreadListener {
private final List<Dimension> unload = Lists.<Dimension>newArrayList();
private final Map<String, Position> warps = Maps.<String, Position>newTreeMap();
private final CommandEnvironment scriptEnv = new CommandEnvironment(this);
// private final String owner;
private final File playerDir;
private final File baseDir;
// private final int port;
private final boolean debug;
private WorldServer space;
private ChannelFuture endpoint;
private ChannelFuture localEndpoint;
private String localUser;
private String message;
private boolean running = true;
private boolean stopped;
@ -140,28 +131,29 @@ public final class Server implements Runnable, IThreadListener {
private int pingTimer;
private int syncTimer;
private int perfTimer;
private int progress = -1;
private int total = 0;
public static void main(String[] args) {
Util.checkOs();
Registry.setup("Server thread");
final Server server = new Server(false);
Registry.addShutdownHook(new Runnable() {
public void run() {
server.stopServer();
}
});
server.run();
}
Server(File dir) {
// this.owner = owner;
this.serverThread = new Thread(this, "Server thread");
this.baseDir = dir; // dir != null ? new File(dir) : null;
this.playerDir = new File(this.baseDir, "players");
this.debug = this.baseDir == null;
// this.port = port;
private Server(boolean debug) {
this.debug = debug;
}
public CommandEnvironment getScriptEnvironment() {
return this.scriptEnv;
}
public boolean isStarted() {
return this.started;
}
public String[] getUsers() {
String[] list = this.debug ? null : this.playerDir.list();
String[] list = this.debug ? null : new File("players").list();
if(list == null) {
list = new String[0];
}
@ -182,8 +174,8 @@ public final class Server implements Runnable, IThreadListener {
public void saveWorldInfo() {
if(!this.debug) {
Region.saveWorldInfo(this.baseDir, this.space.getDayTime(), this.localUser);
WorldServer.saveWarps(this.baseDir, this.warps);
Region.saveWorldInfo(null, this.space.getDayTime(), this.localUser);
WorldServer.saveWarps(this.warps);
}
}
@ -192,7 +184,7 @@ public final class Server implements Runnable, IThreadListener {
return null;
NBTTagCompound tag = null;
try {
File dat = new File(this.playerDir, user + ".nbt");
File dat = new File(new File("players"), user + ".nbt");
if(dat.exists() && dat.isFile()) {
tag = NBTLoader.readGZip(dat);
@ -235,7 +227,7 @@ public final class Server implements Runnable, IThreadListener {
private void startProgress(boolean save, int amount) {
this.setTotal(amount);
this.setProgress(0);
Log.JNI.info((save ? "Speichere" : "Generiere und lade") + " Level " + (this.baseDir == null ? "." : this.baseDir.getName()));
Log.JNI.info((save ? "Speichere" : "Generiere und lade") + " Welt");
}
public void unloadWorld(WorldServer world) {
@ -258,15 +250,15 @@ public final class Server implements Runnable, IThreadListener {
Log.JNI.info("Starte Server Version " + Config.VERSION);
if(!this.debug) {
this.setMessage("Welt wird erstellt und geladen");
FolderInfo info = Region.loadWorldInfo(this.baseDir);
FolderInfo info = Region.loadWorldInfo(null);
// if(dtime == -1L) // {
// dtime = World.START_TIME;
//// Config.set("spawnDim", "1", null);
//// }
this.worlds.add(this.space = new WorldServer(this, this.baseDir, info == null ? World.START_TIME : info.time,
this.worlds.add(this.space = new WorldServer(this, info == null ? World.START_TIME : info.time,
Space.INSTANCE, false));
this.dimensions.put(this.space.dimension.getDimensionId(), this.space);
this.playerDir.mkdirs();
new File("players").mkdirs();
// if(Config.spawnY < 0) {
// WorldServer world = this.getWorld(Config.spawnDim);
// world = world == null ? this.space : world;
@ -297,17 +289,17 @@ public final class Server implements Runnable, IThreadListener {
Config.set("weatherChanges", "false", null);
Config.set("mobSpawning", "false", null);
Config.set("spawnRadius", "0", null);
this.worlds.add(this.space = new WorldServer(this, null, World.START_TIME,
this.worlds.add(this.space = new WorldServer(this, World.START_TIME,
Space.INSTANCE, true));
this.dimensions.put(this.space.dimension.getDimensionId(), this.space);
}
this.setTpsTarget(20.0f);
if(!this.debug) {
for(Dimension dim : UniverseRegistry.getDimensions()) {
if(WorldServer.needsLoading(this.baseDir, dim)) {
if(WorldServer.needsLoading(dim)) {
this.getWorld(dim.getDimensionId()).loadForcedChunks();
}
WorldServer.loadWarps(this.baseDir, dim, this.warps);
WorldServer.loadWarps(dim, this.warps);
}
// this.openLAN();
}
@ -339,17 +331,22 @@ public final class Server implements Runnable, IThreadListener {
this.lastPoll = this.currentTime;
this.ticksDone = 0L;
}
this.started = true;
if(!this.started) {
this.started = true;
this.sendPipeIPC("running", true);
}
}
try {
this.stopServer();
this.stopped = true;
this.sendPipeIPC("running", false);
}
catch(Throwable e) {
Log.JNI.error(e, "Fehler beim Beenden des Servers");
}
finally {
this.stopped = true;
this.sendPipeIPC("running", false);
Log.JNI.info("Server wurde beendet");
}
}
@ -365,8 +362,8 @@ public final class Server implements Runnable, IThreadListener {
bx = bx >> 4;
bz = bz >> 4;
long last = System.currentTimeMillis();
for(int x = -Config.distance; x <= Config.distance && this.running; x++) {
for(int z = -Config.distance; z <= Config.distance && this.running; z++) {
for(int x = -Config.distance; x <= Config.distance; x++) {
for(int z = -Config.distance; z <= Config.distance; z++) {
long time = System.currentTimeMillis();
if(time - last >= 10L) {
this.setProgress(done);
@ -436,7 +433,7 @@ public final class Server implements Runnable, IThreadListener {
}
}
if(++this.syncTimer == 20) {
this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime()));
this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime(), this.getInfo()));
this.syncTimer = 0;
}
this.ticked.clear();
@ -477,7 +474,7 @@ public final class Server implements Runnable, IThreadListener {
Dimension dim = UniverseRegistry.getDimension(dimension);
if(dim == null)
return null;
world = new WorldServer(this, this.baseDir, this.space.getDayTime(),
world = new WorldServer(this, this.space.getDayTime(),
dim, this.debug);
this.worlds.add(world);
this.dimensions.put(dimension, world);
@ -497,11 +494,7 @@ public final class Server implements Runnable, IThreadListener {
public List<WorldServer> getWorlds() {
return this.worlds;
}
public boolean isStopped() {
return this.stopped;
}
public long[] getTickTimes() {
return this.tickTimes;
}
@ -547,41 +540,17 @@ public final class Server implements Runnable, IThreadListener {
public Player getPlayer(String user) {
return this.usermap.get(user);
}
public String getMessage() {
synchronized(this) {
return this.message;
}
}
public int getProgress() {
synchronized(this) {
return this.progress;
}
}
public int getTotal() {
synchronized(this) {
return this.total;
}
}
private void setMessage(String message) {
synchronized(this) {
this.message = message;
}
this.sendPipeIPC("message", message);
}
private void setProgress(int progress) {
synchronized(this) {
this.progress = progress;
}
this.sendPipeIPC("progress", progress);
}
private void setTotal(int total) {
synchronized(this) {
this.total = total;
}
this.sendPipeIPC("total", total);
}
public void setVar(String cv, String value) {
@ -591,11 +560,7 @@ public final class Server implements Runnable, IThreadListener {
}
});
}
public File getFolder() {
return this.baseDir;
}
private <V> ListenableFuture<V> callFromMainThread(Callable<V> callable) {
if(!this.isMainThread() && !this.stopped) {
ListenableFutureTask<V> task = ListenableFutureTask.<V>create(callable);
@ -661,35 +626,33 @@ public final class Server implements Runnable, IThreadListener {
Player conn = new Player(this, connection, loginUser);
if(tag != null)
conn.readFromNBT(tag);
if(!connection.isLocalChannel()) {
if(Config.playerLimit > 0 && this.players.size() >= Config.playerLimit && !conn.getAdmin())
return String.format("Der Server ist voll (%d/%d)!", this.players.size(), Config.playerLimit);
if(Config.auth) {
if(conn.getPassword() == null) {
if(!Config.register)
return "Anmeldung neuer Accounts ist auf diesem Server deaktiviert (Whitelisted)";
if(loginPass.length() == 0)
return "Ein neues Passwort ist erforderlich um diesen Server zu betreten (mindestens 8 Zeichen)";
if(loginPass.length() < 8)
return "Passwort ist zu kurz, mindestens 8 Zeichen";
conn.setPassword(loginPass);
Log.JNI.info(loginUser + " registrierte sich mit Passwort");
}
else if(!conn.getPassword().equals(loginPass)) {
return "Falsches Passwort";
}
else {
Log.JNI.info(loginUser + " loggte sich mit Passwort ein");
}
}
if(Config.compression >= 0) {
connection.sendPacket(new RPacketEnableCompression(Config.compression), new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
connection.setCompressionTreshold(Config.compression);
}
});
}
}
if(Config.playerLimit > 0 && this.players.size() >= Config.playerLimit && !conn.isAdmin())
return String.format("Der Server ist voll (%d/%d)!", this.players.size(), Config.playerLimit);
if(!connection.isLocalChannel() && Config.auth) {
if(conn.getPassword() == null) {
if(!Config.register)
return "Anmeldung neuer Accounts ist auf diesem Server deaktiviert (Whitelisted)";
if(loginPass.length() == 0)
return "Ein neues Passwort ist erforderlich um diesen Server zu betreten (mindestens 8 Zeichen)";
if(loginPass.length() < 8)
return "Passwort ist zu kurz, mindestens 8 Zeichen";
conn.setPassword(loginPass);
Log.JNI.info(loginUser + " registrierte sich mit Passwort");
}
else if(!conn.getPassword().equals(loginPass)) {
return "Falsches Passwort";
}
else {
Log.JNI.info(loginUser + " loggte sich mit Passwort ein");
}
}
if(Config.compression >= 0) {
connection.sendPacket(new RPacketEnableCompression(Config.compression), new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
connection.setCompressionTreshold(Config.compression);
}
});
}
connection.sendPacket(new RPacketLoginSuccess());
connection.setNetHandler(conn);
this.players.add(conn);
@ -766,7 +729,7 @@ public final class Server implements Runnable, IThreadListener {
return null;
NBTTagCompound tag = null;
try {
File dat = new File(this.playerDir, user + ".nbt");
File dat = new File(new File("players"), user + ".nbt");
if(dat.exists() && dat.isFile()) {
tag = NBTLoader.readGZip(dat);
@ -794,8 +757,8 @@ public final class Server implements Runnable, IThreadListener {
// else
// etag = new NBTTagCompound();
conn.writeToNBT(tag);
File tmp = new File(this.playerDir, conn.getUser() + ".nbt.tmp");
File dat = new File(this.playerDir, conn.getUser() + ".nbt");
File tmp = new File(new File("players"), conn.getUser() + ".nbt.tmp");
File dat = new File(new File("players"), conn.getUser() + ".nbt");
NBTLoader.writeGZip(tag, tmp);
if(dat.exists()) {
dat.delete();
@ -1020,7 +983,7 @@ public final class Server implements Runnable, IThreadListener {
}
private void updateTimeAndWeatherForPlayer(Player conn, WorldServer world) {
conn.sendPacket(new SPacketTimeUpdate(world.getDayTime()));
conn.sendPacket(new SPacketTimeUpdate(world.getDayTime(), this.getInfo()));
conn.sendPacket(new S2BPacketChangeGameState(S2BPacketChangeGameState.Action.SET_WEATHER, world.getWeather().getID()));
conn.sendPacket(new S2BPacketChangeGameState(S2BPacketChangeGameState.Action.RAIN_STRENGTH, world.getRainStrength()));
conn.sendPacket(new S2BPacketChangeGameState(S2BPacketChangeGameState.Action.DARKNESS, world.getDarkness()));
@ -1055,41 +1018,21 @@ public final class Server implements Runnable, IThreadListener {
NetConnection manager = new NetConnection();
Server.this.clients.add(manager);
channel.pipeline().addLast((String)"packet_handler", (ChannelHandler)manager);
manager.setNetHandler(new HandshakeHandlerTCP(Server.this, manager));
manager.setNetHandler(new HandshakeHandler(Server.this, manager));
}
}).group(SERVER_NIO_EVENTLOOP.getValue()).localAddress((InetAddress)null, port)).bind().syncUninterruptibly();
}
}
public SocketAddress setLocalEndpoint() {
// ChannelFuture future;
synchronized(this.serverThread) {
if(this.localEndpoint == null) {
// throw new IllegalStateException("Lokaler Eingangspunkt bereits gesetzt");
// }
this.localEndpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(LocalServerChannel.class)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) throws Exception {
NetConnection manager = new NetConnection();
manager.setNetHandler(new HandshakeHandlerMemory(Server.this, manager));
Server.this.clients.add(manager);
channel.pipeline().addLast((String)"packet_handler", (ChannelHandler)manager);
}
}).group((EventLoopGroup)SERVER_NIO_EVENTLOOP.getValue()).localAddress(LocalAddress.ANY)).bind().syncUninterruptibly();
// this.localEndpoint = future;
}
}
return this.localEndpoint.channel().localAddress();
}
private void unsetLanEndpoint() {
for(Player conn : Lists.newArrayList(this.players)) {
if(!conn.isLocal())
conn.disconnect();
}
this.terminateEndpoints(false);
this.terminateEndpoint();
}
private void terminateEndpoints(boolean local) {
private void terminateEndpoint() {
synchronized(this.serverThread) {
if(this.endpoint != null) {
Log.JNI.info("Schließe Port");
@ -1101,15 +1044,6 @@ public final class Server implements Runnable, IThreadListener {
Log.JNI.warn("Unterbrochen beim Schließen des Kanals");
}
}
if(local && this.localEndpoint != null) {
try {
this.localEndpoint.channel().close().sync();
this.localEndpoint = null;
}
catch(InterruptedException e) {
Log.JNI.warn("Unterbrochen beim Schließen des lokalen Kanals");
}
}
}
}
@ -1128,9 +1062,6 @@ public final class Server implements Runnable, IThreadListener {
manager.processReceivedPackets();
}
catch(Exception e) {
if(manager.isLocalChannel()) {
throw e;
}
Log.JNI.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten");
manager.sendPacket(new SPacketDisconnect(), new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) throws Exception {
@ -1148,12 +1079,12 @@ public final class Server implements Runnable, IThreadListener {
public Map<String, Position> getWarps() {
return this.warps;
}
public void start() {
this.serverThread.start();
}
//
// public void start() {
// this.serverThread.start();
// }
void stopServer() {
private void stopServer() {
if(!this.stopped) {
this.setProgress(-1);
this.setMessage("Stoppe server");
@ -1162,7 +1093,7 @@ public final class Server implements Runnable, IThreadListener {
for(Player conn : Lists.newArrayList(this.players)) {
conn.disconnect();
}
this.terminateEndpoints(true);
this.terminateEndpoint();
if(this.started) {
Log.JNI.info("Speichere Spieler");
this.saveAllPlayerData(true);
@ -1203,4 +1134,14 @@ public final class Server implements Runnable, IThreadListener {
}));
this.running = false;
}
public String getInfo() {
return String.format("Server: %.3f T/s (%.1fx), Z: %.1f T/s, D: %.3f ms", this.getTpsRate(), this.getTpsRate() / 20.0f, this.getTpsTarget(), this.getAverageTps()) + "\n" +
"Geladen: " + this.getWorlds().size() + " Welten, " + WorldServer.getLoadedInfo(this);
}
private void sendPipeIPC(String key, Object value) {
System.out.println("#" + key + (value == null ? "" : " " + String.valueOf(value)));
System.out.flush();
}
}