tcr/server/src/main/java/server/Server.java

1357 lines
47 KiB
Java
Raw Normal View History

2025-05-04 20:27:55 +02:00
package server;
2025-03-11 00:23:54 +01:00
2025-05-03 18:01:17 +02:00
import java.io.BufferedInputStream;
import java.io.BufferedReader;
2025-03-11 00:23:54 +01:00
import java.io.File;
import java.io.IOException;
2025-05-03 18:01:17 +02:00
import java.io.InputStreamReader;
2025-03-11 00:23:54 +01:00
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
2025-05-12 18:52:03 +02:00
import java.text.SimpleDateFormat;
2025-03-11 00:23:54 +01:00
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
2025-03-11 00:23:54 +01:00
import java.util.Collections;
2025-05-12 18:52:03 +02:00
import java.util.Date;
2025-03-11 00:23:54 +01:00
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
2025-05-14 14:58:06 +02:00
import java.util.Map.Entry;
2025-03-11 00:23:54 +01:00
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import common.Version;
2025-05-07 18:19:24 +02:00
import common.collect.Lists;
import common.collect.Maps;
import common.color.TextColor;
import common.dimension.Dimension;
import common.dimension.Space;
import common.entity.Entity;
import common.entity.npc.EntityHuman;
import common.entity.npc.EntityNPC;
import common.future.Futures;
import common.future.ListenableFuture;
import common.future.ListenableFutureTask;
import common.future.ThreadFactoryBuilder;
import common.init.Config;
import common.init.EntityRegistry;
import common.init.Registry;
import common.init.UniverseRegistry;
2025-05-14 14:58:06 +02:00
import common.init.Config.ValueType;
2025-05-07 18:19:24 +02:00
import common.log.Log;
import common.net.bootstrap.ServerBootstrap;
import common.net.channel.Channel;
import common.net.channel.ChannelException;
import common.net.channel.ChannelFuture;
import common.net.channel.ChannelFutureListener;
import common.net.channel.ChannelHandler;
import common.net.channel.ChannelInitializer;
import common.net.channel.ChannelOption;
import common.net.channel.nio.NioEventLoopGroup;
import common.net.channel.socket.nio.NioServerSocketChannel;
import common.net.handler.timeout.ReadTimeoutHandler;
import common.net.util.concurrent.Future;
import common.net.util.concurrent.GenericFutureListener;
2025-05-07 18:19:24 +02:00
import common.network.IPlayer;
import common.network.IThreadListener;
import common.network.NetConnection;
import common.network.Packet;
import common.network.PacketDecoder;
import common.network.PacketEncoder;
import common.network.PacketPrepender;
import common.network.PacketSplitter;
import common.network.NetHandler.ThreadQuickExitException;
import common.packet.RPacketEnableCompression;
import common.packet.RPacketLoginSuccess;
2025-05-24 20:38:49 +02:00
import common.packet.SPacketEntityEffect;
import common.packet.SPacketChangeGameState;
import common.packet.SPacketPlayerListItem;
import common.packet.SPacketPlayerAbilities;
2025-05-07 18:19:24 +02:00
import common.packet.SPacketDisconnect;
import common.packet.SPacketHeldItemChange;
import common.packet.SPacketJoinGame;
import common.packet.SPacketRespawn;
import common.packet.SPacketSetExperience;
import common.packet.SPacketSkin;
import common.packet.SPacketTimeUpdate;
import common.packet.SPacketWorld;
import common.potion.PotionEffect;
2025-05-27 21:47:36 +02:00
import common.tags.TagObject;
2025-05-08 12:37:48 +02:00
import common.util.BlockPos;
import common.util.EncryptUtil;
2025-05-07 18:19:24 +02:00
import common.util.ExtMath;
import common.util.LazyLoader;
2025-05-08 12:37:48 +02:00
import common.util.PortalType;
import common.util.Position;
2025-05-07 18:19:24 +02:00
import common.util.Util;
2025-05-08 12:37:48 +02:00
import common.util.WorldPos;
2025-05-07 18:19:24 +02:00
import common.world.World;
2025-05-14 15:12:11 +02:00
import server.biome.GenBiome;
2025-05-13 17:19:40 +02:00
import server.clipboard.ReorderRegistry;
import server.clipboard.RotationRegistry;
2025-05-05 18:27:06 +02:00
import server.command.CommandEnvironment;
2025-05-14 14:58:06 +02:00
import server.command.Executor;
2025-05-05 18:27:06 +02:00
import server.command.FixedExecutor;
import server.network.HandshakeHandler;
import server.network.Player;
import server.network.User;
2025-05-12 18:52:03 +02:00
import server.world.Converter;
2025-05-13 17:02:57 +02:00
import server.world.Region;
import server.world.WorldServer;
2025-03-11 00:23:54 +01:00
2025-05-13 17:02:57 +02:00
public final class Server implements IThreadListener {
private static final LazyLoader<NioEventLoopGroup> SERVER_NIO_EVENTLOOP = new LazyLoader<NioEventLoopGroup>() {
2025-03-11 00:23:54 +01:00
protected NioEventLoopGroup load() {
return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
}
};
2025-05-01 15:13:38 +02:00
private final Thread serverThread = Thread.currentThread();
2025-03-11 00:23:54 +01:00
private final List<NetConnection> clients = Collections.<NetConnection>synchronizedList(Lists.<NetConnection>newArrayList());
private final List<Player> players = Lists.<Player>newArrayList();
private final Map<String, Player> online = Maps.<String, Player>newHashMap();
private final Map<String, User> users = Maps.newTreeMap();
2025-03-11 00:23:54 +01:00
private final Queue<FutureTask<?>> queue = new ArrayDeque<FutureTask<?>>();
private final long[] tickTimes = new long[100];
private final Map<Integer, WorldServer> dimensions = Maps.newTreeMap();
private final List<WorldServer> worlds = Lists.<WorldServer>newArrayList();
private final List<WorldServer> ticked = Lists.<WorldServer>newArrayList();
private final List<Dimension> unload = Lists.<Dimension>newArrayList();
private final Map<String, Position> warps = Maps.<String, Position>newTreeMap();
2025-03-25 23:10:40 +01:00
private final CommandEnvironment scriptEnv = new CommandEnvironment(this);
2025-03-11 00:23:54 +01:00
private final boolean debug;
2025-05-29 03:04:44 +02:00
private KeyPair keyPair;
2025-03-11 00:23:54 +01:00
private WorldServer space;
private ChannelFuture endpoint;
2025-05-14 14:58:06 +02:00
2025-03-11 00:23:54 +01:00
private boolean running = true;
private boolean stopped;
private boolean started;
2025-05-14 14:58:06 +02:00
private String endMessage = "Server beendet";
2025-03-11 00:23:54 +01:00
private long currentTime = System.nanoTime() / 1000L;
private long tpsTarget;
private long ticksDone;
private long ticksTodo;
private long timePassed;
private long lastSchedule;
private long lastPoll;
private long tpsRate;
private long lastWarning;
private int saveTimer;
private int pingTimer;
private int syncTimer;
private int perfTimer;
2025-05-01 15:13:38 +02:00
public static void main(String[] args) {
2025-05-18 18:47:20 +02:00
long time = System.currentTimeMillis();
Util.checkPlatform();
Log.init();
2025-05-01 15:13:38 +02:00
Registry.setup("Server thread");
Log.SYSTEM.info("Starte " + Version.NAME + " Server Version " + Util.VERSION + " (Protokoll #" + Util.PROTOCOL + ")");
2025-05-14 15:12:11 +02:00
GenBiome.setAsProvider();
2025-05-14 00:37:46 +02:00
UniverseRegistry.register();
2025-05-13 17:19:40 +02:00
RotationRegistry.register();
ReorderRegistry.register();
2025-05-03 18:01:17 +02:00
boolean debug = System.getProperty("server.debug", null) != null;
2025-05-14 16:01:08 +02:00
final Server server = new Server(debug);
2025-05-01 15:13:38 +02:00
Registry.addShutdownHook(new Runnable() {
public void run() {
server.stopServer();
}
});
Log.setSync(server);
server.run(time);
2025-05-03 18:01:17 +02:00
Region.killIO();
Log.flushLog();
2025-05-01 15:13:38 +02:00
}
2025-03-11 00:23:54 +01:00
2025-05-29 03:04:44 +02:00
public static void saveServerConfig(long time, Server server) {
2025-05-27 21:46:30 +02:00
TagObject data = new TagObject();
2025-05-12 18:52:03 +02:00
data.setLong("Time", time);
data.setLong("LastAccess", System.currentTimeMillis());
data.setString("Version", Util.VERSION);
2025-05-27 21:46:30 +02:00
TagObject cfg = new TagObject();
2025-05-12 18:52:03 +02:00
for(String cvar : Config.VARS.keySet()) {
Config.Value value = Config.VARS.get(cvar);
if(!value.noDef || !value.def.equals(value.getValue()))
cfg.setString(cvar, value.getValue());
2025-05-12 18:52:03 +02:00
}
2025-05-27 22:57:25 +02:00
data.setObject("Config", cfg);
data.setObject("Universe", UniverseRegistry.toTags());
2025-05-29 03:04:44 +02:00
if(server != null) {
data.setByteArray("PrivateKey", server.getPrivateKey().getEncoded());
data.setByteArray("PublicKey", server.getPublicKey().getEncoded());
}
File nfile = new File("server.cdt.tmp");
File lfile = new File("server.cdt");
2025-05-12 18:52:03 +02:00
try {
TagObject.writeGZip(data, nfile);
2025-05-12 18:52:03 +02:00
if(lfile.exists())
lfile.delete();
nfile.renameTo(lfile);
}
catch(Exception e) {
Log.IO.error(e, "Fehler beim Schreiben von " + nfile);
}
}
2025-05-29 03:04:44 +02:00
public long loadServerConfig() {
2025-05-12 18:52:03 +02:00
Config.clear();
UniverseRegistry.clear();
File file = new File("server.cdt");
2025-05-12 18:52:03 +02:00
if(!file.exists())
file = new File("server.cdt.tmp");
2025-05-12 18:52:03 +02:00
if(file.exists()) {
try {
TagObject tag = TagObject.readGZip(file);
2025-05-27 22:57:25 +02:00
TagObject cfg = tag.getObject("Config");
for(String key : cfg.keySet()) {
2025-05-13 17:02:57 +02:00
Config.set(key, cfg.getString(key), false);
2025-05-12 18:52:03 +02:00
}
UniverseRegistry.fromTags(tag.getObject("Universe"));
2025-05-12 18:52:03 +02:00
long lastPlayed = tag.getLong("LastAccess");
2025-05-27 21:25:11 +02:00
String version = tag.hasString("Version") ? tag.getString("Version") : null;
2025-05-12 18:52:03 +02:00
version = version != null && version.isEmpty() ? "<unbekannt>" : version;
2025-05-27 21:25:11 +02:00
long time = tag.hasLong("Time") ? tag.getLong("Time") : World.START_TIME;
2025-05-12 18:52:03 +02:00
Log.IO.info("Version: %s", version);
Log.IO.info("Weltzeit: %d Ticks / %d Sekunden", time, time / 20L);
Log.IO.info("Zuletzt geladen: %s", new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date(lastPlayed)));
2025-05-29 03:04:44 +02:00
if(tag.hasByteArray("PrivateKey") && tag.hasByteArray("PublicKey")) {
PrivateKey key = EncryptUtil.decodePrivateKey(tag.getByteArray("PrivateKey"));
PublicKey pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("PublicKey"));
if(key != null && pubkey != null)
this.keyPair = new KeyPair(pubkey, key);
}
2025-05-12 18:52:03 +02:00
return time;
}
catch(Exception e) {
Log.IO.error(e, "Fehler beim Lesen von " + file);
Config.clear();
UniverseRegistry.clear();
}
}
Log.IO.info("Erstelle neue Welt und Konfiguration");
return World.START_TIME;
}
2025-05-14 16:01:08 +02:00
private Server(boolean debug) {
2025-05-01 15:13:38 +02:00
this.debug = debug;
2025-05-13 17:02:57 +02:00
Config.setCallback(new Runnable() {
public void run() {
for(WorldServer world : Server.this.getWorlds()) {
world.updatePhysics();
}
Server.this.sendPacket(new SPacketWorld(WorldServer.clampGravity(), Config.dayCycle, Config.timeFlow));
}
}, "daylightCycle", "timeFlow", "gravity");
Config.setCallback(new Runnable() {
public void run() {
for(WorldServer world : Server.this.getWorlds()) {
world.updateViewRadius();
}
}
}, "viewDistance");
Config.setCallback(new Runnable() {
public void run() {
Server.this.bind(Config.port);
}
}, "port");
2025-05-26 20:29:08 +02:00
Config.setCallback(new Runnable() {
public void run() {
if((!Config.password.isEmpty() && Config.password.length() < 8) || Config.password.length() > IPlayer.MAX_PASS_LENGTH) {
2025-05-26 20:29:08 +02:00
Log.IO.error("Passwort muss aus 8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen bestehen");
Config.set("password", "", false);
}
}
}, "password");
2025-03-11 00:23:54 +01:00
}
2025-03-25 23:10:40 +01:00
public CommandEnvironment getScriptEnvironment() {
2025-03-18 01:29:36 +01:00
return this.scriptEnv;
}
2025-03-11 00:23:54 +01:00
public List<String> getPlayerFilenames() {
2025-05-01 15:13:38 +02:00
String[] list = this.debug ? null : new File("players").list();
if(list == null)
return Lists.newArrayList();
List<String> names = Lists.newArrayList();
2025-03-11 00:23:54 +01:00
for(int i = 0; i < list.length; ++i) {
if(list[i].endsWith(".cdt")) {
String name = list[i].substring(0, list[i].length() - 4);
if(IPlayer.isValidUser(name))
names.add(name);
2025-03-11 00:23:54 +01:00
}
}
return names;
2025-03-11 00:23:54 +01:00
}
public void saveWorldInfo() {
if(!this.debug) {
2025-05-29 03:04:44 +02:00
saveServerConfig(this.space.getDayTime(), this);
2025-05-01 15:13:38 +02:00
WorldServer.saveWarps(this.warps);
2025-03-11 00:23:54 +01:00
}
}
2025-05-27 21:46:30 +02:00
public TagObject loadPlayerData(String user) {
2025-05-05 18:27:06 +02:00
if(this.debug || !IPlayer.isValidUser(user))
2025-03-11 00:23:54 +01:00
return null;
2025-05-27 21:46:30 +02:00
TagObject tag = null;
2025-03-11 00:23:54 +01:00
try {
File dat = new File(new File("players"), user + ".cdt");
2025-03-11 00:23:54 +01:00
if(dat.exists() && dat.isFile()) {
tag = TagObject.readGZip(dat);
2025-03-11 00:23:54 +01:00
}
}
catch(Exception e) {
2025-05-25 18:30:12 +02:00
Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht laden");
2025-03-11 00:23:54 +01:00
}
return tag;
}
2025-05-27 21:46:30 +02:00
public void writePlayerData(String user, TagObject tag) {
if(this.debug || !IPlayer.isValidUser(user))
return;
try {
File tmp = new File(new File("players"), user + ".cdt.tmp");
File dat = new File(new File("players"), user + ".cdt");
TagObject.writeGZip(tag, tmp);
if(dat.exists()) {
dat.delete();
}
tmp.renameTo(dat);
}
catch(Exception e) {
2025-05-25 18:30:12 +02:00
Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht speichern");
}
}
public Position getOfflinePosition(String user) {
2025-05-27 21:46:30 +02:00
TagObject tag = this.loadPlayerData(user);
2025-03-11 00:23:54 +01:00
if(tag == null)
return null;
double posX = tag.getDouble("PosX");
double posY = tag.getDouble("PosY");
double posZ = tag.getDouble("PosZ");
float rotYaw = tag.getFloat("Yaw");
float rotPitch = tag.getFloat("Pitch");
2025-05-27 21:25:11 +02:00
int dimension = tag.getInt("Dimension");
2025-03-11 00:23:54 +01:00
return new Position(posX, posY, posZ, rotYaw, rotPitch, dimension);
}
public void saveAllWorlds(boolean message) {
2025-03-11 00:23:54 +01:00
if(this.debug)
return;
2025-05-14 16:01:08 +02:00
if(message)
2025-05-25 18:30:12 +02:00
Log.TICK.info("Speichere Welt");
2025-03-11 00:23:54 +01:00
this.saveWorldInfo();
for(WorldServer world : this.worlds) {
world.saveAllChunks();
}
}
public void unloadWorld(WorldServer world) {
if(world != this.space)
this.unloadWorld(world.dimension);
}
private void unloadWorld(Dimension dim) {
WorldServer world = this.dimensions.get(dim.getDimensionId());
if(world != null && world.players.isEmpty()) {
world.saveAllChunks();
Region.finishWrite();
this.worlds.remove(world);
this.dimensions.remove(dim.getDimensionId());
}
}
2025-05-14 14:58:06 +02:00
public boolean setVar(Executor exec, String line) {
if(line.length() < 1) {
for(Entry<String, Config.Value> entry : Config.VARS.entrySet()) {
Config.Value cvar = entry.getValue();
String v = cvar.getValue();
String comp = TextColor.YELLOW + entry.getKey() + TextColor.GRAY + " = ";
if(cvar.noDef && cvar.def.equals(v))
comp += TextColor.DGRAY + "[ - ]";
else if(entry.getKey().equals("password") && !v.isEmpty())
2025-05-14 14:58:06 +02:00
comp += TextColor.NEON + "'****'";
else if(cvar.type == ValueType.STRING)
comp += TextColor.NEON + "'" + v + "'";
else
comp += ((cvar.type == ValueType.BOOLEAN ? (v.equals("true") ? TextColor.GREEN : TextColor.RED) : TextColor.BLUE)) + v;
if(!cvar.def.equals(v)) {
comp += TextColor.GRAY + " (" + (cvar.noDef ? TextColor.DGRAY + "[ - ]" : TextColor.BROWN + cvar.def) + TextColor.GRAY + ")";
2025-05-14 14:58:06 +02:00
}
exec.logConsole(comp);
}
exec.logConsole(TextColor.GREEN + "SVARs insgesamt registriert: %d", Config.VARS.size());
return true;
}
line = line.trim();
String[] args = /* line.isEmpty() ? new String[0] : */ line.split(" ", -1);
if(args.length == 1) {
// case 0:
// break;
// case 1:
Config.Value cfg = Config.VARS.get(args[0]);
if(cfg == null)
return false;
String v = cfg.getValue();
String comp = TextColor.YELLOW + args[0] + TextColor.GRAY + " = ";
if(cfg.noDef && cfg.def.equals(v))
comp += TextColor.DGRAY + "[ - ]";
else if(cfg.type == ValueType.STRING)
2025-05-14 14:58:06 +02:00
comp += TextColor.NEON + "'" + v + "'";
else
comp += ((cfg.type == ValueType.BOOLEAN ? (v.equals("true") ? TextColor.GREEN : TextColor.RED) : TextColor.BLUE)) + v;
if(!cfg.def.equals(v))
comp += TextColor.GRAY + " (" + (cfg.noDef ? TextColor.DGRAY + "[ - ]" : TextColor.BROWN + cfg.def) + TextColor.GRAY + ")";
2025-05-14 14:58:06 +02:00
exec.logConsole(comp);
// break;
// default:
}
else {
Config.Value cv = Config.VARS.get(args[0]);
if(cv == null)
return false;
String value = args[1];
if(cv.type == ValueType.STRING && "\"\"".equals(value)) {
value = "";
}
// else if(cv.type == ValueType.BOOLEAN && "toggle".equalsIgnoreCase(value)) {
// value = "" + !Boolean.parseBoolean(cv.getValue());
// }
// else
if(cv.type == ValueType.BOOLEAN && !"true".equals(value) && !"false".equals(value)) {
if(!value.equalsIgnoreCase("true") && !value.equalsIgnoreCase("false")) {
exec.logConsole(TextColor.DRED + "'%s' ist nicht 'true' oder 'false'", value);
return true;
}
value = value.toLowerCase();
}
if(cv.type == ValueType.INTEGER) {
try {
Integer.parseInt(value);
}
catch(NumberFormatException e) {
exec.logConsole(TextColor.DRED + "'%s' ist keine gültige Zahl", value);
return true;
}
}
else if(cv.type == ValueType.FLOAT) {
try {
Float.parseFloat(value);
}
catch(NumberFormatException e) {
exec.logConsole(TextColor.DRED + "'%s' ist keine gültige Zahl", value);
return true;
}
}
Config.set(args[0], value, true);
2025-05-26 20:29:08 +02:00
exec.logConsole(TextColor.YELLOW + "%s" + TextColor.GRAY + " -> " + (cv.noDef && cv.def.equals(cv.getValue()) ? TextColor.DGRAY + "[ - ]" : ((cv.type == ValueType.BOOLEAN ? (cv.getValue().equals("true") ? TextColor.GREEN : TextColor.RED) : (cv.type == ValueType.STRING ? TextColor.NEON : TextColor.BLUE))) + "%s"), args[0], cv.type == ValueType.STRING ? ("'" + cv.getValue() + "'") : cv.getValue());
2025-05-14 14:58:06 +02:00
}
return true;
}
public void run(long time) {
2025-03-11 00:23:54 +01:00
if(!this.debug) {
2025-05-12 18:52:03 +02:00
Converter.convert();
2025-05-29 03:04:44 +02:00
long wtime = this.loadServerConfig();
if(this.keyPair == null) {
Log.SYSTEM.info("Generiere neues Schlüsselpaar");
2025-05-30 01:43:37 +02:00
this.keyPair = EncryptUtil.createKeypair();
2025-05-29 03:04:44 +02:00
}
User.loadDatabase(this.users);
2025-03-11 00:23:54 +01:00
// if(dtime == -1L) // {
// dtime = World.START_TIME;
//// Config.set("spawnDim", "1", null);
//// }
2025-05-12 18:52:03 +02:00
this.worlds.add(this.space = new WorldServer(this, wtime,
2025-03-11 00:23:54 +01:00
Space.INSTANCE, false));
this.dimensions.put(this.space.dimension.getDimensionId(), this.space);
2025-05-01 15:13:38 +02:00
new File("players").mkdirs();
2025-03-11 00:23:54 +01:00
// if(Config.spawnY < 0) {
// WorldServer world = this.getWorld(Config.spawnDim);
// world = world == null ? this.space : world;
// Random rand = new Random(world.getSeed());
// int x = 0;
// int z = 0;
// int count = 0;
// while(!(world.getGroundAboveSeaLevel(new BlockPos(x, 0, z)) == Blocks.grass)) {
// if(count == 1000) {
// SKC.warn("Konnte keine Spawn-Position finden");
// break;
// }
// x += rand.zrange(64) - rand.zrange(64);
// z += rand.zrange(64) - rand.zrange(64);
// ++count;
// }
// Config.set("spawnX", "" + x, null);
// Config.set("spawnY", "" + world.getSeaLevel(), null);
// Config.set("spawnZ", "" + z, null);
// Config.set("spawnYaw", "" + (-180.0f + rand.floatv() * 360.0f), null);
// Config.set("spawnPitch", "0.0", null);
// }
}
else {
2025-05-29 03:04:44 +02:00
Log.SYSTEM.info("Generiere temporäres Schlüsselpaar");
2025-05-30 01:43:37 +02:00
this.keyPair = EncryptUtil.createKeypair();
2025-03-11 00:23:54 +01:00
Config.clear();
UniverseRegistry.clear();
2025-05-13 17:02:57 +02:00
Config.set("daylightCycle", "false", false);
Config.set("weatherChanges", "false", false);
Config.set("mobSpawning", "false", false);
Config.set("spawnRadius", "0", false);
2025-05-01 15:13:38 +02:00
this.worlds.add(this.space = new WorldServer(this, World.START_TIME,
2025-03-11 00:23:54 +01:00
Space.INSTANCE, true));
this.dimensions.put(this.space.dimension.getDimensionId(), this.space);
}
this.setTpsTarget(20.0f);
if(!this.debug) {
for(Dimension dim : UniverseRegistry.getDimensions()) {
2025-05-01 15:13:38 +02:00
if(WorldServer.needsLoading(dim)) {
2025-03-11 00:23:54 +01:00
this.getWorld(dim.getDimensionId()).loadForcedChunks();
}
2025-05-01 15:13:38 +02:00
WorldServer.loadWarps(dim, this.warps);
2025-03-11 00:23:54 +01:00
}
}
if(Config.port >= 0)
this.bind(Config.port);
else
Log.SYSTEM.warn("Kein Port definiert, verwende 'port <1024-32767>' um einen Hosting-Port festzulegen");
if(Config.accessRequired && Config.password.length() < 8)
2025-05-26 20:29:08 +02:00
Log.SYSTEM.warn("Kein Passwort definiert, verwende 'password <8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen>' um ein Zugangspasswort festzulegen");
2025-05-03 18:01:17 +02:00
Thread con = new Thread(new Runnable() {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(System.in)));
public void run() {
while(true) {
String line;
try {
line = this.reader.readLine();
}
catch(IOException e) {
line = null;
}
if(line == null)
break;
2025-05-03 22:42:03 +02:00
final String cmd = line;
Server.this.schedule(new Runnable() {
public void run() {
2025-05-14 14:58:06 +02:00
FixedExecutor exec = new FixedExecutor(Server.this, "#con", "KONSOLE", null);
if(!Server.this.setVar(exec, cmd))
Server.this.scriptEnv.execute(cmd, exec);
2025-05-03 22:42:03 +02:00
}
});
2025-05-03 18:01:17 +02:00
}
}
}, "Server console handler");
con.setDaemon(true);
con.start();
2025-05-25 18:30:12 +02:00
Log.SYSTEM.info("Server gestartet in " + String.format("%.1f", (double)(System.currentTimeMillis() - time) / 1000.0) + " Sekunden");
2025-03-11 00:23:54 +01:00
while(this.running) {
this.currentTime = System.nanoTime() / 1000L;
this.timePassed = this.currentTime - this.lastSchedule;
this.lastSchedule = this.currentTime;
if(this.timePassed < 0L) {
2025-05-25 18:30:12 +02:00
Log.TICK.warn("Zeit lief rückwärts! Hat sich die Systemzeit geändert?");
2025-03-11 00:23:54 +01:00
this.timePassed = 0L;
this.ticksTodo = 0L;
}
if(this.timePassed > 2000000L && this.currentTime - this.lastWarning >= 15000000L) {
2025-05-25 18:30:12 +02:00
Log.TICK.warn("Kann Server-Tick nicht aufrecht erhalten! Hat sich die Systemzeit geändert, oder ist der Server überlastet? " +
2025-03-11 00:23:54 +01:00
"Liege " + (this.timePassed / 1000L) + " ms zurück, überspringe " + ((this.timePassed * this.tpsTarget) / 1000000000L) + " Tick(s)");
this.timePassed = 0L;
this.lastWarning = this.currentTime;
this.ticksTodo = 0L;
}
this.ticksTodo += (this.timePassed * this.tpsTarget) / 1000L;
while(this.ticksTodo >= 1000000L && (System.nanoTime() / 1000L - this.currentTime) < 10000L) {
this.ticksTodo -= 1000000L;
this.tick();
++this.ticksDone;
}
if((this.currentTime - this.lastPoll) >= 1000000L) {
this.tpsRate = (this.ticksDone * 1000000000L) / (this.currentTime - this.lastPoll);
this.lastPoll = this.currentTime;
this.ticksDone = 0L;
}
2025-05-14 16:01:08 +02:00
this.started = true;
2025-03-11 00:23:54 +01:00
}
try {
this.stopServer();
this.stopped = true;
}
catch(Throwable e) {
2025-05-25 18:30:12 +02:00
Log.SYSTEM.error(e, "Fehler beim Beenden des Servers");
2025-03-11 00:23:54 +01:00
}
finally {
this.stopped = true;
2025-05-25 18:30:12 +02:00
Log.SYSTEM.info("Server wurde beendet");
2025-03-11 00:23:54 +01:00
}
}
2025-03-16 23:14:24 +01:00
2025-05-14 16:01:08 +02:00
public void preload(WorldServer world, int bx, int bz) {
2025-03-16 23:14:24 +01:00
int done = 0;
int total = Config.distance * 2 + 1;
total *= total;
2025-05-25 18:30:12 +02:00
Log.TICK.info("Generiere und lade Welt");
2025-03-28 14:30:37 +01:00
// WorldServer world = this.getWorld(Config.spawnDim);
// world = world == null ? this.space : world;
bx = bx >> 4;
bz = bz >> 4;
2025-03-16 23:14:24 +01:00
long last = System.currentTimeMillis();
2025-05-01 15:13:38 +02:00
for(int x = -Config.distance; x <= Config.distance; x++) {
for(int z = -Config.distance; z <= Config.distance; z++) {
2025-03-16 23:14:24 +01:00
long time = System.currentTimeMillis();
2025-05-14 16:01:08 +02:00
if(time - last > 1000L) {
2025-05-25 18:30:12 +02:00
Log.TICK.info("Bereite Spawnbereich vor" + ": " + (done * 100 / total) + "%");
2025-05-14 16:01:08 +02:00
last = time;
2025-03-16 23:14:24 +01:00
}
++done;
world.loadChunk(bx + x, bz + z);
}
}
}
2025-03-28 14:30:37 +01:00
// public void resetProgress() {
// this.setProgress(-1);
// }
//
// public void setDone() {
// this.setProgress(-2);
// }
2025-03-11 00:23:54 +01:00
private void tick() {
// boolean pause = this.paused;
// this.paused = this.pause;
// if(!pause && this.paused) {
// if(!this.debug)
// Log.info("Speichere und pausiere Spiel...");
// this.saveAllPlayerData(true);
// this.saveAllWorlds(true);
// }
// if(this.paused) {
// synchronized(this.queue) {
// while(!this.queue.isEmpty()) {
// FutureTask<?> task = this.queue.poll();
// try {
// task.run();
// task.get();
// }
// catch(ExecutionException e1) {
// if(!(e1.getCause() instanceof ThreadQuickExitException))
// Log.error("Fehler beim Ausführen von Pause-Task " + task, e1);
// }
// catch(InterruptedException e2) {
// Log.error("Fehler beim Ausführen von Pause-Task " + task, e2);
// }
// }
// }
// return;
// }
long now = System.currentTimeMillis();
synchronized(this.queue) {
while(!this.queue.isEmpty()) {
FutureTask<?> task = this.queue.poll();
try {
task.run();
task.get();
}
catch(ExecutionException e1) {
if(!(e1.getCause() instanceof ThreadQuickExitException))
2025-05-25 18:30:12 +02:00
Log.SYSTEM.error(e1, "Fehler beim Ausführen von Server-Task " + task); // throw new RuntimeException(e1);
2025-03-11 00:23:54 +01:00
}
catch(InterruptedException e2) {
2025-05-25 18:30:12 +02:00
Log.SYSTEM.error(e2, "Fehler beim Ausführen von Server-Task " + task);
2025-03-11 00:23:54 +01:00
}
}
}
if(++this.syncTimer == 20) {
2025-05-01 15:13:38 +02:00
this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime(), this.getInfo()));
2025-03-11 00:23:54 +01:00
this.syncTimer = 0;
}
this.ticked.clear();
this.ticked.addAll(this.worlds);
for(WorldServer world : this.ticked) {
world.tick();
world.updateEntities();
world.updateTrackedEntities();
if(world != this.space && world.shouldUnload())
this.unload.add(world.dimension);
}
for(Dimension dim : this.unload) {
this.unloadWorld(dim);
}
this.networkTick();
if(++this.pingTimer > 600) {
2025-05-24 20:38:49 +02:00
this.sendPacket(new SPacketPlayerListItem((List)this.getPlayers()));
2025-03-11 00:23:54 +01:00
this.pingTimer = 0;
}
if(Config.saveInterval > 0 && ++this.saveTimer >= Config.saveInterval) {
this.saveAllPlayerData(false);
this.saveAllWorlds(false);
this.saveTimer = 0;
}
Log.flushLog();
2025-03-11 00:23:54 +01:00
this.tickTimes[this.perfTimer++] = System.currentTimeMillis() - now;
if(this.perfTimer == 100) {
this.perfTimer = 0;
}
}
public WorldServer getSpace() {
return this.space;
}
public WorldServer getWorld(int dimension) {
WorldServer world = this.dimensions.get(dimension);
if(world == null) {
Dimension dim = UniverseRegistry.getDimension(dimension);
if(dim == null)
return null;
2025-05-01 15:13:38 +02:00
world = new WorldServer(this, this.space.getDayTime(),
2025-03-11 00:23:54 +01:00
dim, this.debug);
this.worlds.add(world);
this.dimensions.put(dimension, world);
}
return world;
}
public WorldServer getWorldNoLoad(int dimension) {
return this.dimensions.get(dimension);
}
public WorldServer getWorld(String alias) {
Dimension dim = UniverseRegistry.getDimension(alias);
return dim == null ? null : this.getWorld(dim.getDimensionId());
}
public List<WorldServer> getWorlds() {
return this.worlds;
}
2025-05-01 15:13:38 +02:00
public void resetSaveTimer() {
this.saveTimer = 0;
}
2025-03-11 00:23:54 +01:00
public long[] getTickTimes() {
return this.tickTimes;
}
public float getAverageTps() {
long total = 0L;
for(int z = 0; z < this.tickTimes.length; z++) {
total += this.tickTimes[z];
}
return (float)total / (float)this.tickTimes.length;
}
public long getLastTick() {
return this.tickTimes[this.perfTimer == 0 ? 99 : (this.perfTimer - 1)];
}
public float getTpsRate() {
return ((float)this.tpsRate) / 1000.0f;
}
public float getTpsTarget() {
return ((float)this.tpsTarget) / 1000.0f;
}
public void setTpsTarget(float target) {
this.tpsTarget = this.tpsRate = (long)(target * 1000.0f);
this.currentTime = this.lastSchedule = this.lastPoll = System.nanoTime() / 1000L;
this.timePassed = this.ticksTodo = this.ticksDone = 0L;
}
public List<String> getAllPlayerNames() {
2025-03-11 00:23:54 +01:00
List<String> list = new ArrayList<String>(this.players.size());
for(int z = 0; z < this.players.size(); z++) {
list.add(this.players.get(z).getUser());
}
return list;
}
public List<String> getAllUserNames() {
return Lists.newArrayList(this.users.keySet());
}
public List<Player> getPlayers() {
2025-03-11 00:23:54 +01:00
return this.players;
}
public Collection<User> getUsers() {
return this.users.values();
}
public Player getPlayer(String user) {
return this.online.get(user);
}
public User getUser(String user) {
return this.users.get(user);
2025-03-11 00:23:54 +01:00
}
2025-05-01 15:13:38 +02:00
2025-03-11 00:23:54 +01:00
private <V> ListenableFuture<V> callFromMainThread(Callable<V> callable) {
if(!this.isMainThread() && !this.stopped) {
ListenableFutureTask<V> task = ListenableFutureTask.<V>create(callable);
synchronized(this.queue) {
this.queue.add(task);
return task;
}
}
else {
try {
return Futures.<V>immediateFuture(callable.call());
}
catch(Exception exception) {
2025-05-21 18:45:45 +02:00
Log.SYSTEM.error(exception, "Fehler beim sofortigen Ausführen von Server-Task " + callable);
2025-03-11 00:23:54 +01:00
return Futures.immediateFailedFuture(exception);
}
}
}
public ListenableFuture<Object> schedule(Runnable run) {
return this.<Object>callFromMainThread(Executors.callable(run));
}
public boolean isMainThread() {
return Thread.currentThread() == this.serverThread;
}
2025-03-28 14:30:37 +01:00
// private void movePlayerToSpawn(EntityNPC player) {
// BlockPos pos = new BlockPos(Config.spawnX, Config.spawnY, Config.spawnZ);
// int radius = Config.spawnRadius;
// if(radius > 0) {
// pos = ((WorldServer)player.worldObj).getTopSolidOrLiquidBlock(pos.add(
// ExtMath.clampi(player.worldObj.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1), 0,
// ExtMath.clampi(player.worldObj.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1)));
// }
// player.moveToBlockPosAndAngles(pos, radius > 0 ? (-180.0f + player.worldObj.rand.floatv() * 360.0f) : Config.spawnYaw,
// radius > 0 ? 0.0f : Config.spawnPitch);
// while(radius >= 0 && !player.worldObj.getCollidingBoundingBoxes(player, player.getEntityBoundingBox()).isEmpty()
// && player.posY < 511.0D) {
// player.setPosition(player.posX, player.posY + 1.0D, player.posZ);
// }
// }
2025-03-27 17:54:03 +01:00
2025-03-28 14:30:37 +01:00
public Position getRandomSpawnPosition(WorldPos origin) {
WorldServer world = this.getWorld(origin.getDimension());
2025-03-27 17:54:03 +01:00
world = world == null ? this.space : world;
2025-03-28 14:30:37 +01:00
BlockPos pos = origin;
2025-03-27 17:54:03 +01:00
int radius = Config.spawnRadius;
if(radius > 0) {
pos = world.getTopSolidOrLiquidBlock(pos.add(
ExtMath.clampi(world.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1), 0,
ExtMath.clampi(world.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1)));
}
int y = pos.getY();
while(world.getState(new BlockPos(pos.getX(), y, pos.getZ())).getBlock().getMaterial().blocksMovement() && y < 511)
y++;
return new Position(pos.getX() + 0.5d, (double)y, pos.getZ() + 0.5d,
radius > 0 ? (-180.0f + world.rand.floatv() * 360.0f) : Config.spawnYaw,
radius > 0 ? 0.0f : Config.spawnPitch, world.dimension.getDimensionId());
}
public void addUser(User user) {
this.users.put(user.getUser(), user);
}
2025-03-11 00:23:54 +01:00
public void addPlayer(NetConnection connection, String loginUser) {
2025-05-27 21:46:30 +02:00
TagObject tag = this.readPlayer(loginUser);
Player conn = new Player(this, connection, loginUser);
2025-03-11 00:23:54 +01:00
if(tag != null)
conn.readTags(tag);
2025-05-01 15:13:38 +02:00
if(Config.compression >= 0) {
connection.sendPacket(new RPacketEnableCompression(Config.compression), new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
connection.setCompressionTreshold(Config.compression);
}
});
}
2025-05-13 17:02:57 +02:00
connection.sendPacket(new RPacketLoginSuccess(this.debug));
2025-03-11 00:23:54 +01:00
connection.setNetHandler(conn);
this.players.add(conn);
User user = this.users.remove(loginUser);
if(user != null)
conn.copyFrom(user);
this.users.put(loginUser, conn);
this.online.put(loginUser, conn);
2025-03-11 00:23:54 +01:00
2025-03-27 17:54:03 +01:00
tag = conn.readCharacter();
2025-05-27 21:25:11 +02:00
WorldServer world = tag == null ? this.space : this.getWorld(tag.getInt("Dimension"));
2025-03-11 00:23:54 +01:00
world = world == null ? this.space : world;
EntityNPC player = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(EntityHuman.class) : tag.getString("id"));
if(tag != null)
player.readTags(tag);
2025-03-11 00:23:54 +01:00
else
player.onInitialSpawn(null);
// player.setWorld(world);
if(tag == null)
2025-03-27 17:54:03 +01:00
/* this.movePlayerToSpawn(player); */ player.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f);
2025-03-11 00:23:54 +01:00
2025-05-25 18:30:12 +02:00
Log.NETWORK.info(loginUser + "[" + connection.getCutAddress() + "] hat sich mit Objekt-ID "
2025-03-11 00:23:54 +01:00
+ player.getId() + " auf Level " + world.dimension.getDimensionId() + ": "
2025-03-28 19:45:12 +01:00
+ String.format("%.2f %.2f %.2f", player.posX, player.posY, player.posZ) + " verbunden (" + (tag == null ? "Charakter-Editor" : "'" + player.getCommandName() + "'") + ")");
2025-03-11 00:23:54 +01:00
2025-03-27 17:54:03 +01:00
conn.sendPacket(new SPacketJoinGame(player.getId(), world.dimension, EntityRegistry.getEntityID(player), tag == null));
2025-03-11 00:23:54 +01:00
conn.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem));
conn.sendPacket(new SPacketWorld(WorldServer.clampGravity(),
Config.dayCycle, Config.timeFlow));
// conn.initializeStats();
2025-05-24 20:38:49 +02:00
this.sendPacket(new SPacketPlayerListItem(false, conn));
2025-03-11 00:23:54 +01:00
world.spawnEntityInWorld(player);
this.preparePlayer(player, null);
for(int i = 0; i < this.players.size(); ++i) {
Player other = this.players.get(i);
2025-05-24 20:38:49 +02:00
conn.sendPacket(new SPacketPlayerListItem(false, other));
2025-03-11 00:23:54 +01:00
}
conn.sendPacket(new SPacketSkin(player.getId(), player.getSkin())); // , player.getModel()));
conn.setPlayerLocation(player.posX, player.posY, player.posZ, player.rotYaw, player.rotPitch);
this.updateTimeAndWeatherForPlayer(conn, world);
for(PotionEffect effect : player.getEffects()) {
2025-05-24 20:38:49 +02:00
conn.sendPacket(new SPacketEntityEffect(player.getId(), effect));
2025-03-11 00:23:54 +01:00
}
2025-05-24 20:38:49 +02:00
conn.sendPacket(new SPacketPlayerAbilities(player));
2025-03-11 00:23:54 +01:00
conn.addSelfToInternalCraftingInventory();
conn.onConnect();
}
public void removePlayer(Player conn) {
2025-03-11 00:23:54 +01:00
EntityNPC player = conn.getEntity();
player.unmount();
this.writePlayer(conn);
2025-05-13 17:02:57 +02:00
WorldServer world = (WorldServer)player.getServerWorld();
2025-03-11 00:23:54 +01:00
world.removeEntity(player);
world.removePlayer(player);
this.players.remove(conn);
this.online.remove(conn.getUser());
User user = new User(conn.getUser());
user.copyFrom(conn);
this.users.put(conn.getUser(), user);
2025-05-24 20:38:49 +02:00
this.sendPacket(new SPacketPlayerListItem(true, conn));
2025-03-11 00:23:54 +01:00
}
private void preparePlayer(EntityNPC player, WorldServer oldWorld) {
2025-05-13 17:02:57 +02:00
WorldServer newWorld = (WorldServer)player.getServerWorld();
2025-03-11 00:23:54 +01:00
if(oldWorld != null) {
oldWorld.removePlayer(player);
}
newWorld.addPlayer(player);
newWorld.loadChunk((int)player.posX >> 4, (int)player.posZ >> 4);
}
2025-05-27 21:46:30 +02:00
private TagObject readPlayer(String user) {
2025-03-11 00:23:54 +01:00
if(this.debug)
return null;
2025-05-27 21:46:30 +02:00
TagObject tag = null;
2025-03-11 00:23:54 +01:00
try {
File dat = new File(new File("players"), user + ".cdt");
2025-03-11 00:23:54 +01:00
if(dat.exists() && dat.isFile()) {
tag = TagObject.readGZip(dat);
2025-03-11 00:23:54 +01:00
}
}
catch(Exception e) {
2025-05-25 18:30:12 +02:00
Log.IO.error(e, "Konnte Spielerdaten für " + user + " nicht laden");
2025-03-11 00:23:54 +01:00
}
return tag;
}
private void writePlayer(Player conn) {
2025-03-11 00:23:54 +01:00
if(this.debug)
return;
try {
2025-05-27 21:46:30 +02:00
TagObject tag = new TagObject();
2025-03-27 17:54:03 +01:00
EntityNPC entity = conn.getPresentEntity();
if(entity != null) {
2025-05-27 21:46:30 +02:00
TagObject etag = new TagObject();
entity.writeTags(etag);
2025-05-27 21:25:11 +02:00
etag.setInt("Dimension", entity.worldObj.dimension.getDimensionId());
2025-03-27 17:54:03 +01:00
etag.setString("id", EntityRegistry.getEntityString(entity));
conn.writeCharacter(etag);
}
conn.writeTags(tag);
File tmp = new File(new File("players"), conn.getUser() + ".cdt.tmp");
File dat = new File(new File("players"), conn.getUser() + ".cdt");
TagObject.writeGZip(tag, tmp);
2025-03-11 00:23:54 +01:00
if(dat.exists()) {
dat.delete();
}
tmp.renameTo(dat);
}
catch(Exception e) {
2025-05-25 18:30:12 +02:00
Log.IO.error(e, "Konnte Spielerdaten für " + conn.getUser() + " nicht speichern");
2025-03-11 00:23:54 +01:00
}
}
public void recreatePlayer(Player conn) {
2025-03-11 00:23:54 +01:00
EntityNPC old = conn.getEntity();
BlockPos pos = old.getPosition();
2025-05-14 00:37:46 +02:00
WorldServer oldWorld = (WorldServer)old.getServerWorld();
oldWorld.removePlayerFromTrackers(old);
oldWorld.untrackEntity(old);
oldWorld.removePlayer(old);
oldWorld.removePlayerEntityDangerously(old);
2025-03-11 00:23:54 +01:00
WorldPos bed = old.getSpawnPoint();
2025-03-28 14:30:37 +01:00
WorldPos origin = old.getOrigin();
2025-03-11 00:23:54 +01:00
BlockPos spawn = null;
String message = null;
WorldServer world = bed == null ? this.space : this.getWorld(bed.getDimension());
if(world == null) {
world = this.space;
bed = null;
message = "Die Dimension deines Einstiegspunkts wurde gelöscht oder zerstört";
}
else if(bed != null) {
spawn = EntityNPC.getBedSpawnLocation(world, bed);
if(spawn == null) {
world = this.space;
bed = null;
message = "Dein Einstiegspunkt wurde blockiert oder dein Bett zerstört";
}
}
if(bed == null) {
2025-03-28 14:30:37 +01:00
world = this.getWorld(origin.getDimension());
2025-03-11 00:23:54 +01:00
world = world == null ? this.space : world;
}
EntityNPC nplayer = conn.createPlayer(world, EntityRegistry.getEntityString(old));
conn.clonePlayer(old);
nplayer.setId(old.getId());
2025-03-28 14:30:37 +01:00
nplayer.setOrigin(origin);
2025-03-11 00:23:54 +01:00
if(bed != null) {
nplayer.setLocationAndAngles((double)((float)spawn.getX() + 0.5F), (double)((float)spawn.getY() + 0.1F),
(double)((float)spawn.getZ() + 0.5F), 0.0F, 0.0F);
nplayer.setSpawnPoint(bed);
}
else {
2025-03-28 14:30:37 +01:00
Position rpos = this.getRandomSpawnPosition(origin);
nplayer.setLocationAndAngles(rpos.x(), rpos.y(), rpos.z(), rpos.yaw(), rpos.pitch());
2025-03-28 14:30:37 +01:00
// this.movePlayerToSpawn(nplayer);
2025-03-11 00:23:54 +01:00
}
world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4);
if(bed != null ? Config.checkBed : Config.spawnRadius >= 0) {
while(!world.getCollidingBoundingBoxes(nplayer, nplayer.getEntityBoundingBox()).isEmpty() && nplayer.posY < 512.0D) {
nplayer.setPosition(nplayer.posX, nplayer.posY + 1.0D, nplayer.posZ);
}
}
2025-03-27 17:54:03 +01:00
conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer), conn.isInEditor()));
2025-03-11 00:23:54 +01:00
conn.setPlayerLocation(nplayer.posX, nplayer.posY, nplayer.posZ, nplayer.rotYaw, nplayer.rotPitch);
conn.sendPacket(new SPacketSetExperience(nplayer.experience, nplayer.experienceTotal, nplayer.experienceLevel));
this.updateTimeAndWeatherForPlayer(conn, world);
world.addPlayer(nplayer);
world.spawnEntityInWorld(nplayer);
conn.addSelfToInternalCraftingInventory();
nplayer.setHealth(nplayer.getHealth());
conn.sendPlayerAbilities();
if(message != null)
conn.addHotbar(TextColor.DRED + message);
2025-03-11 00:23:54 +01:00
conn.addFeed(TextColor.RED + "* Bei %d, %d, %d in Dimension %s gestorben", pos.getX(), pos.getY(), pos.getZ(),
old.worldObj.dimension.getFormattedName(false));
}
2025-05-27 21:46:30 +02:00
public TagObject swapPlayer(Player conn, TagObject tag, Class<? extends EntityNPC> clazz) {
2025-03-11 00:23:54 +01:00
EntityNPC old = conn.getEntity();
old.unmount();
2025-05-27 21:46:30 +02:00
TagObject oldTag = new TagObject();
old.writeTags(oldTag);
2025-05-27 21:25:11 +02:00
oldTag.setInt("Dimension", old.worldObj.dimension.getDimensionId());
2025-03-11 00:23:54 +01:00
oldTag.setString("id", EntityRegistry.getEntityString(old));
2025-05-14 00:37:46 +02:00
WorldServer oldWorld = (WorldServer)old.getServerWorld();
oldWorld.removePlayerFromTrackers(old);
oldWorld.untrackEntity(old);
oldWorld.removePlayer(old);
oldWorld.removePlayerEntityDangerously(old);
2025-03-11 00:23:54 +01:00
// old.dead = false;
2025-05-27 21:25:11 +02:00
WorldServer world = tag == null ? this.space : this.getWorld(tag.getInt("Dimension"));
2025-03-11 00:23:54 +01:00
world = world == null ? this.space : world;
2025-03-27 17:54:03 +01:00
EntityNPC nplayer = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(clazz) : tag.getString("id"));
2025-03-11 00:23:54 +01:00
// conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer)));
if(tag != null)
nplayer.readTags(tag);
2025-03-11 00:23:54 +01:00
else
nplayer.onInitialSpawn(null);
// nplayer.clonePlayer(old);
nplayer.setId(old.getId());
if(tag == null)
2025-03-27 17:54:03 +01:00
/* this.movePlayerToSpawn(nplayer); */ nplayer.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f);
2025-03-11 00:23:54 +01:00
world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4);
world.addPlayer(nplayer);
world.spawnEntityInWorld(nplayer);
2025-03-27 17:54:03 +01:00
conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer), conn.isInEditor()));
2025-03-11 00:23:54 +01:00
conn.sendPacket(new SPacketSkin(nplayer.getId(), nplayer.getSkin())); // , nplayer.getModel()));
conn.setPlayerLocation(nplayer.posX, nplayer.posY, nplayer.posZ, nplayer.rotYaw, nplayer.rotPitch);
conn.sendPacket(new SPacketSetExperience(nplayer.experience, nplayer.experienceTotal, nplayer.experienceLevel));
// conn.initializeStats();
conn.addSelfToInternalCraftingInventory();
nplayer.setHealth(nplayer.getHealth());
this.updateTimeAndWeatherForPlayer(conn, world);
this.syncPlayerInventory(nplayer);
for(PotionEffect effect : nplayer.getEffects()) {
2025-05-24 20:38:49 +02:00
conn.sendPacket(new SPacketEntityEffect(nplayer.getId(), effect));
2025-03-11 00:23:54 +01:00
}
conn.sendPlayerAbilities();
2025-03-27 17:54:03 +01:00
return oldTag;
2025-03-11 00:23:54 +01:00
}
public void transferToDimension(EntityNPC player, int dimension, BlockPos pos, float yaw, float pitch, PortalType portal) {
2025-05-13 17:02:57 +02:00
WorldServer oldWorld = (WorldServer)player.getServerWorld(); // this.getWorld(player.dimension);
2025-03-11 00:23:54 +01:00
// player.dimension = dimension;
WorldServer newWorld = this.getWorld(dimension);
2025-03-27 17:54:03 +01:00
player.connection.sendPacket(new SPacketRespawn(newWorld.dimension, EntityRegistry.getEntityID(player), player.connection.isInEditor()));
2025-03-11 00:23:54 +01:00
oldWorld.removePlayerEntityDangerously(player);
player.dead = false;
this.placeInDimension(player, oldWorld, newWorld, pos, portal);
if(player.isEntityAlive()) {
newWorld.spawnEntityInWorld(player);
newWorld.updateEntity(player, false);
}
// player.loadedChunks.clear(); // FIX ??
this.preparePlayer(player, oldWorld);
player.connection.setPlayerLocation(player.posX, player.posY, player.posZ, pos != null ? yaw : player.rotYaw, pos != null ? pitch : player.rotPitch);
if(pos != null)
player.setRotationYawHead(yaw);
// player.interactManager.setWorld(newWorld);
2025-05-05 18:27:06 +02:00
this.updateTimeAndWeatherForPlayer((Player)player.connection, newWorld);
2025-03-11 00:23:54 +01:00
this.syncPlayerInventory(player);
for(PotionEffect effect : player.getEffects()) {
2025-05-24 20:38:49 +02:00
player.connection.sendPacket(new SPacketEntityEffect(player.getId(), effect));
2025-03-11 00:23:54 +01:00
}
player.connection.sendPlayerAbilities();
}
public void placeInDimension(Entity entity, WorldServer oldWorld, WorldServer world, BlockPos pos, PortalType portal) {
double newX = entity.posX;
double newY = entity.posY;
double newZ = entity.posZ;
float newYaw = entity.rotYaw;
float newPitch = entity.rotPitch;
if(pos != null) {
newX = ((double)pos.getX()) + 0.5;
newY = pos.getY();
newZ = ((double)pos.getZ()) + 0.5;
}
else {
pos = world.getTopSolidOrLiquidBlock(new BlockPos(newX, 0, newZ));
newX = ((double)pos.getX()) + 0.5;
newY = (double)pos.getY();
newZ = ((double)pos.getZ()) + 0.5;
}
newX = (double)ExtMath.clampd(newX, -World.MAX_SIZE + 1, World.MAX_SIZE - 1);
newZ = (double)ExtMath.clampd(newZ, -World.MAX_SIZE + 1, World.MAX_SIZE - 1);
entity.setLocationAndAngles(newX, newY, newZ, newYaw, newPitch);
if(entity.isEntityAlive()) {
oldWorld.updateEntity(entity, false);
}
if(entity.isEntityAlive()) {
entity.setLocationAndAngles(newX, entity.posY, newZ, entity.rotYaw, entity.rotPitch);
if(portal != null)
world.makePortal(new BlockPos(ExtMath.floord(entity.posX)
,ExtMath.floord(entity.posY)
,ExtMath.floord(entity.posZ)), 128, portal);
}
entity.setWorld(world);
}
public void sendPacket(Packet packet) {
for(Player conn : this.players) {
2025-03-11 00:23:54 +01:00
conn.sendPacket(packet);
}
}
public void sendPacket(Packet packet, int dimension) {
for(Player conn : this.players) {
2025-03-11 00:23:54 +01:00
if(conn.getEntity() != null && conn.getEntity().worldObj.dimension.getDimensionId() == dimension) {
conn.sendPacket(packet);
}
}
}
public void sendNear(double x, double y, double z, double radius, int dimension, Packet packet) {
this.sendNearExcept(null, x, y, z, radius, dimension, packet);
}
public void sendNearExcept(EntityNPC except, double x, double y, double z, double radius, int dimension, Packet packet) {
for(Player conn : this.players) {
2025-03-11 00:23:54 +01:00
EntityNPC player = conn.getEntity();
if(player != null && player != except && player.worldObj.dimension.getDimensionId() == dimension) {
double dx = x - player.posX;
double dy = y - player.posY;
double dz = z - player.posZ;
if(dx * dx + dy * dy + dz * dz < radius * radius) {
conn.sendPacket(packet);
}
}
}
}
public void saveAllPlayerData(boolean message) {
if(this.debug)
return;
if(message) {
2025-05-25 18:30:12 +02:00
Log.TICK.info("Speichere Spielerdaten");
2025-03-11 00:23:54 +01:00
}
for(Player conn : this.players) {
2025-03-11 00:23:54 +01:00
this.writePlayer(conn);
}
User.saveDatabase(this.users);
2025-03-11 00:23:54 +01:00
}
private void updateTimeAndWeatherForPlayer(Player conn, WorldServer world) {
2025-05-01 15:13:38 +02:00
conn.sendPacket(new SPacketTimeUpdate(world.getDayTime(), this.getInfo()));
2025-05-24 20:38:49 +02:00
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.SET_WEATHER, world.getWeather().getID()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.RAIN_STRENGTH, world.getRainStrength()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.DARKNESS, world.getDarkness()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.FOG_STRENGTH, world.getFogStrength()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.TEMPERATURE, world.getTempOffset()));
2025-03-11 00:23:54 +01:00
}
public void syncPlayerInventory(EntityNPC player) {
player.connection.sendContainerToPlayer(player.inventoryContainer);
player.connection.setPlayerHealthUpdated();
player.connection.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem));
}
2025-05-14 14:58:06 +02:00
private void terminateEndpoint(String message) {
if(this.started)
for(Player conn : Lists.newArrayList(this.players)) {
conn.disconnect(message);
}
2025-03-11 00:23:54 +01:00
synchronized(this.serverThread) {
if(this.endpoint != null) {
2025-05-25 18:30:12 +02:00
Log.NETWORK.info("Schließe Port");
2025-03-11 00:23:54 +01:00
try {
this.endpoint.channel().close().sync();
this.endpoint = null;
}
catch(InterruptedException e) {
2025-05-25 18:30:12 +02:00
Log.NETWORK.warn("Unterbrochen beim Schließen des Kanals");
2025-03-11 00:23:54 +01:00
}
}
}
}
private void networkTick() {
synchronized(this.clients) {
Iterator<NetConnection> iter = this.clients.iterator();
while(iter.hasNext()) {
final NetConnection manager = iter.next();
if(!manager.hasNoChannel()) {
if(!manager.isChannelOpen()) {
iter.remove();
manager.checkDisconnected();
}
else {
try {
manager.processReceivedPackets();
}
catch(Exception e) {
2025-05-25 18:30:12 +02:00
Log.NETWORK.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten");
2025-05-14 14:58:06 +02:00
manager.sendPacket(new SPacketDisconnect(e.getMessage()), new GenericFutureListener<Future<? super Void>>() {
2025-03-11 00:23:54 +01:00
public void operationComplete(Future<? super Void> future) throws Exception {
manager.closeChannel("Fehlerhaftes Datenpaket");
}
});
manager.disableAutoRead();
}
}
}
}
}
}
public Map<String, Position> getWarps() {
return this.warps;
}
2025-05-01 15:13:38 +02:00
//
// public void start() {
// this.serverThread.start();
// }
2025-03-11 00:23:54 +01:00
2025-05-01 15:13:38 +02:00
private void stopServer() {
2025-03-11 00:23:54 +01:00
if(!this.stopped) {
2025-05-25 18:30:12 +02:00
Log.SYSTEM.info("Beende Server");
2025-05-14 14:58:06 +02:00
this.terminateEndpoint(this.endMessage);
2025-03-11 00:23:54 +01:00
if(this.started) {
this.saveAllPlayerData(true);
this.saveAllWorlds(true);
Region.finishWrite();
}
}
}
public void bind(int port) {
2025-05-14 14:58:06 +02:00
synchronized(this.serverThread) {
if(port >= 0) {
try {
if(this.endpoint != null)
this.terminateEndpoint("Wechsele auf Port " + port);
// throw new IllegalStateException("Eingangspunkt bereits gesetzt");
2025-05-25 18:30:12 +02:00
Log.NETWORK.info("Öffne Port %d auf 0.0.0.0 (Timeout %ds)", port, Config.timeout);
2025-05-14 14:58:06 +02:00
this.endpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) throws Exception {
try {
channel.config().setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(true));
}
catch(ChannelException e) {
}
channel.pipeline().addLast((String)"timeout", (ChannelHandler)(new ReadTimeoutHandler(Config.timeout)))
.addLast((String)"splitter", (ChannelHandler)(new PacketSplitter()))
.addLast((String)"decoder", (ChannelHandler)(new PacketDecoder(true)))
.addLast((String)"prepender", (ChannelHandler)(new PacketPrepender()))
.addLast((String)"encoder", (ChannelHandler)(new PacketEncoder(false)));
NetConnection manager = new NetConnection();
Server.this.clients.add(manager);
channel.pipeline().addLast((String)"packet_handler", (ChannelHandler)manager);
manager.setNetHandler(new HandshakeHandler(Server.this, manager));
}
}).group(SERVER_NIO_EVENTLOOP.getValue()).localAddress((InetAddress)null, port)).bind().syncUninterruptibly();
}
catch(Throwable e) {
2025-05-25 18:30:12 +02:00
Log.NETWORK.error(e, "**** KONNTE NICHT AN PORT " + port + " ANBINDEN!");
2025-05-14 14:58:06 +02:00
}
2025-03-11 00:23:54 +01:00
}
2025-05-14 14:58:06 +02:00
else {
if(this.endpoint != null)
this.terminateEndpoint("Trenne Verbindung");
2025-05-03 11:53:23 +02:00
}
}
2025-03-11 00:23:54 +01:00
}
2025-05-14 14:58:06 +02:00
public void shutdown(String message) {
2025-03-11 00:23:54 +01:00
this.running = false;
2025-05-14 14:58:06 +02:00
this.endMessage = message;
2025-03-11 00:23:54 +01:00
}
2025-05-01 15:13:38 +02:00
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);
}
2025-05-03 22:42:03 +02:00
public void logConsole(String message) {
2025-05-14 16:01:08 +02:00
Log.CONSOLE.info(message);
2025-05-03 22:42:03 +02:00
}
public PublicKey getPublicKey() {
return this.keyPair.getPublic();
}
public PrivateKey getPrivateKey() {
return this.keyPair.getPrivate();
}
2025-03-11 00:23:54 +01:00
}