package server; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.InetAddress; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import common.Version; import common.collect.Lists; import common.collect.Maps; import common.color.TextColor; import common.dimension.Dimension; import common.dimension.Space; import common.effect.StatusEffect; 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.EntityRegistry; import common.init.Registry; import common.init.UniverseRegistry; 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; 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; import common.packet.SPacketEntityEffect; import common.packet.SPacketChangeGameState; import common.packet.SPacketPlayerListItem; import common.packet.SPacketPlayerAbilities; import common.packet.SPacketDisconnect; import common.packet.SPacketHeldItemChange; import common.packet.SPacketJoinGame; import common.packet.SPacketRespawn; import common.packet.SPacketServerConfig; import common.packet.SPacketSetExperience; import common.packet.SPacketSkin; import common.packet.SPacketTimeUpdate; import common.tags.TagObject; import common.util.BlockPos; import common.util.EncryptUtil; import common.util.ExtMath; import common.util.LazyLoader; import common.util.Pair; import common.util.PortalType; import common.util.Position; import common.util.Util; import common.util.Var; import common.util.WorldPos; import common.vars.Vars; import common.world.World; import server.biome.GenBiome; import server.clipboard.ReorderRegistry; import server.clipboard.RotationRegistry; import server.command.CommandEnvironment; import server.command.Executor; import server.network.HandshakeHandler; import server.network.Player; import server.network.User; import server.vars.SVar; import server.vars.SVars; import server.world.Region; import server.world.WorldServer; public final class Server implements IThreadListener, Executor { private static final LazyLoader SERVER_NIO_EVENTLOOP = new LazyLoader() { protected NioEventLoopGroup load() { return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); } }; private final Thread serverThread = Thread.currentThread(); private final Map variables = Maps.newTreeMap(); private final List clients = Collections.synchronizedList(Lists.newArrayList()); private final List players = Lists.newArrayList(); private final Map online = Maps.newHashMap(); private final Map users = Maps.newTreeMap(); private final Queue> queue = new ArrayDeque>(); private final long[] tickTimes = new long[100]; private final Map dimensions = Maps.newTreeMap(); private final List worlds = Lists.newArrayList(); private final List ticked = Lists.newArrayList(); private final List unload = Lists.newArrayList(); private final Map warps = Maps.newTreeMap(); private final CommandEnvironment scriptEnv = new CommandEnvironment(this); private KeyPair keyPair; private WorldServer space; private ChannelFuture endpoint; private Position execPos; private boolean running = true; private boolean stopped; private boolean started; private String endMessage = "Server beendet"; 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; public static void main(String[] args) { long time = System.currentTimeMillis(); Util.checkPlatform(); Thread.currentThread().setName("Server thread"); Locale.setDefault(Locale.ROOT); Util.setupHandlers(); Log.init(); Log.SYSTEM.info("Java " + System.getProperty("java.version")); Log.SYSTEM.info("Starte " + Version.NAME + " Server " + Util.VERSION + " (Protokoll #" + Util.PROTOCOL + ")"); if(Util.DEVMODE) Log.SYSTEM.warn("Entwicklungsmodus aktiv - Debugging-Features sind verfügbar"); Registry.register(); GenBiome.setAsProvider(); UniverseRegistry.register(); RotationRegistry.register(); ReorderRegistry.register(); final Server server = new Server(); Util.addShutdownHook(new Runnable() { public void run() { server.stopServer(); Log.flushLog(); } }); Log.setSync(server); server.run(time); Region.killIO(); Log.flushLog(); } private void saveServerConfig(long time) { TagObject data = new TagObject(); data.setLong("Time", time); data.setLong("LastAccess", System.currentTimeMillis()); data.setString("Version", Util.VERSION); TagObject cfg = new TagObject(); for(String cvar : this.variables.keySet()) { SVar value = this.variables.get(cvar); if(!value.noDef || !value.def.equals(value.get())) cfg.setString(cvar, value.get()); } data.setObject("Config", cfg); data.setObject("Universe", UniverseRegistry.toTags()); if(this.keyPair != null) { data.setByteArray("PrivateKey", this.keyPair.getPrivate().getEncoded()); data.setByteArray("PublicKey", this.keyPair.getPublic().getEncoded()); } File nfile = new File("server.cdt.tmp"); File lfile = new File("server.cdt"); try { TagObject.writeGZip(data, nfile); if(lfile.exists()) lfile.delete(); nfile.renameTo(lfile); } catch(Exception e) { Log.IO.error(e, "Fehler beim Schreiben von " + nfile); } } private long loadServerConfig() { File file = new File("server.cdt"); if(!file.exists()) file = new File("server.cdt.tmp"); if(file.exists()) { try { TagObject tag = TagObject.readGZip(file); TagObject cfg = tag.getObject("Config"); for(String key : cfg.keySet()) { SVar svar = this.variables.get(key); if(svar != null) svar.set(cfg.getString(key), false, false); } UniverseRegistry.fromTags(tag.getObject("Universe")); long lastPlayed = tag.getLong("LastAccess"); String version = tag.hasString("Version") ? tag.getString("Version") : null; version = version != null && version.isEmpty() ? "" : version; long time = tag.hasLong("Time") ? tag.getLong("Time") : World.START_TIME; Log.IO.info("Config-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))); if(System.getProperty("server.regenkey") == null && 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); } return time; } catch(Exception e) { Log.IO.error(e, "Fehler beim Lesen von " + file); for(SVar svar : this.variables.values()) { svar.set(svar.def, svar.noDef, false); } } } Log.IO.info("Erstelle neue Welt und Konfiguration"); return World.START_TIME; } private void setCallback(Runnable callback, String ... vars) { for(String key : vars) { this.variables.get(key).setCallback(callback); } } public Map getVariables() { return this.variables; } private Server() { for(Class clazz : new Class[] {Vars.class, SVars.class}) { for(Field field : clazz.getDeclaredFields()) { if(field.isAnnotationPresent(Var.class)) { if(!Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) throw new IllegalArgumentException("Feld für Variable " + field + " muss statisch und änderbar sein!"); Var value = field.getAnnotation(Var.class); if(value.name().isEmpty()) throw new IllegalArgumentException("Variablenname von " + field + " kann nicht leer sein!"); if(this.variables.containsKey(value.name())) throw new IllegalArgumentException("Variable " + value.name() + " existiert bereits!"); this.variables.put(value.name(), new SVar(field, value, clazz == Vars.class)); } } } this.setCallback(new Runnable() { public void run() { for(WorldServer world : Server.this.getWorlds()) { world.updateViewRadius(); } } }, "viewDistance"); this.setCallback(new Runnable() { public void run() { Server.this.bind(SVars.port); } }, "port"); this.setCallback(new Runnable() { public void run() { if((!SVars.password.isEmpty() && SVars.password.length() < 8) || SVars.password.length() > IPlayer.MAX_PASS_LENGTH) Log.IO.warn("Passwort muss aus 8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen bestehen, Login wird nicht möglich sein"); } }, "password"); } public CommandEnvironment getScriptEnvironment() { return this.scriptEnv; } public List getPlayerFilenames() { String[] list = new File("players").list(); if(list == null) return Lists.newArrayList(); List names = Lists.newArrayList(); 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); } } return names; } public void saveWorldInfo() { this.saveServerConfig(this.space.getDayTime()); WorldServer.saveWarps(this.warps); } public TagObject loadPlayerData(String user) { if(!IPlayer.isValidUser(user)) return null; TagObject tag = null; try { File dat = new File(new File("players"), user + ".cdt"); if(dat.exists() && dat.isFile()) { tag = TagObject.readGZip(dat); } } catch(Exception e) { Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht laden"); } return tag; } public void writePlayerData(String user, TagObject tag) { if(!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) { Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht speichern"); } } public Position getOfflinePosition(String user) { TagObject tag = this.loadPlayerData(user); 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"); Dimension dimension = UniverseRegistry.getDimension(tag.getString("Dimension")); return dimension == null ? null : new Position(posX, posY, posZ, rotYaw, rotPitch, dimension); } public void saveAllWorlds(boolean message) { if(message) Log.TICK.info("Speichere Welt"); 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(UniverseRegistry.getId(dim)); if(world != null && world.players.isEmpty()) { world.saveAllChunks(); Region.finishWrite(); this.worlds.remove(world); this.dimensions.remove(UniverseRegistry.getId(dim)); } } public void run(long time) { Region.loadMap(); long wtime = this.loadServerConfig(); if(this.keyPair == null) { Log.SYSTEM.info("Generiere neues Schlüsselpaar"); this.keyPair = EncryptUtil.createKeypair(); } User.loadDatabase(this.users); this.worlds.add(this.space = new WorldServer(this, wtime, Space.INSTANCE)); this.dimensions.put(UniverseRegistry.getId(this.space.dimension), this.space); new File("players").mkdirs(); this.setTpsTarget(20.0f); for(Dimension dim : UniverseRegistry.getDimensions()) { if(WorldServer.needsLoading(dim)) { this.getWorld(dim).loadForcedChunks(); } WorldServer.loadWarps(dim, this.warps); } if(SVars.port >= 0) this.bind(SVars.port); else Log.SYSTEM.warn("Kein Port definiert, verwende 'sv port <1024-32767>' um einen Hosting-Port festzulegen"); if(SVars.accessRequired && SVars.password.length() < 8) Log.SYSTEM.warn("Kein Passwort definiert, verwende 'sv password <8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen>' um ein Zugangspasswort festzulegen"); 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; final String cmd = line; Server.this.schedule(new Runnable() { public void run() { Server.this.scriptEnv.execute(cmd, Server.this); } }); } } }, "Server console handler"); con.setDaemon(true); con.start(); Log.SYSTEM.info("Server gestartet in " + String.format("%.1f", (double)(System.currentTimeMillis() - time) / 1000.0) + " Sekunden"); while(this.running) { this.currentTime = System.nanoTime() / 1000L; this.timePassed = this.currentTime - this.lastSchedule; this.lastSchedule = this.currentTime; if(this.timePassed < 0L) { Log.TICK.warn("Zeit lief rückwärts! Hat sich die Systemzeit geändert?"); this.timePassed = 0L; this.ticksTodo = 0L; } if(this.timePassed > 2000000L && this.currentTime - this.lastWarning >= 15000000L) { Log.TICK.warn("Kann Server-Tick nicht aufrecht erhalten! Hat sich die Systemzeit geändert, oder ist der Server überlastet? " + "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; } this.started = true; } try { this.stopServer(); this.stopped = true; } catch(Throwable e) { Log.SYSTEM.error(e, "Fehler beim Beenden des Servers"); } finally { this.stopped = true; Log.SYSTEM.info("Server wurde beendet"); } } public void preload(WorldServer world, int bx, int bz) { int done = 0; int total = Vars.distance * 2 + 1; total *= total; Log.TICK.info("Generiere und lade Welt"); bx = bx >> 4; bz = bz >> 4; long last = System.currentTimeMillis(); for(int x = -Vars.distance; x <= Vars.distance; x++) { for(int z = -Vars.distance; z <= Vars.distance; z++) { long time = System.currentTimeMillis(); if(time - last > 1000L) { Log.TICK.info("Bereite Spawnbereich vor" + ": " + (done * 100 / total) + "%"); last = time; } ++done; world.loadChunk(bx + x, bz + z); } } } private void tick() { 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)) Log.SYSTEM.error(e1, "Fehler beim Ausführen von Server-Task " + task); // throw new RuntimeException(e1); } catch(InterruptedException e2) { Log.SYSTEM.error(e2, "Fehler beim Ausführen von Server-Task " + task); } } } if(++this.syncTimer == 20) { this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime(), this.getInfo())); 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) { this.sendPacket(new SPacketPlayerListItem((List)this.getPlayers())); this.pingTimer = 0; } if(SVars.saveInterval > 0 && ++this.saveTimer >= SVars.saveInterval) { this.saveAllPlayerData(false); this.saveAllWorlds(false); this.saveTimer = 0; } Log.flushLog(); this.tickTimes[this.perfTimer++] = System.currentTimeMillis() - now; if(this.perfTimer == 100) { this.perfTimer = 0; } } public WorldServer getSpace() { return this.space; } public WorldServer getWorld(Dimension dim) { if(dim == null) return null; WorldServer world = this.dimensions.get(UniverseRegistry.getId(dim)); if(world == null) { world = new WorldServer(this, this.space.getDayTime(), dim); this.worlds.add(world); this.dimensions.put(UniverseRegistry.getId(dim), world); } return world; } public WorldServer getWorldNoLoad(Dimension dim) { return dim == null ? null : this.dimensions.get(UniverseRegistry.getId(dim)); } public WorldServer getWorld(String alias) { Dimension dim = UniverseRegistry.getDimension(alias); return dim == null ? null : this.getWorld(dim); } public List getWorlds() { return this.worlds; } public void resetSaveTimer() { this.saveTimer = 0; } 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 getAllPlayerNames() { List list = new ArrayList(this.players.size()); for(int z = 0; z < this.players.size(); z++) { list.add(this.players.get(z).getUser()); } return list; } public List getAllUserNames() { return Lists.newArrayList(this.users.keySet()); } public List getPlayers() { return this.players; } public Collection 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); } private ListenableFuture callFromMainThread(Callable callable) { if(!this.isMainThread() && !this.stopped) { ListenableFutureTask task = ListenableFutureTask.create(callable); synchronized(this.queue) { this.queue.add(task); return task; } } else { try { return Futures.immediateFuture(callable.call()); } catch(Exception exception) { Log.SYSTEM.error(exception, "Fehler beim sofortigen Ausführen von Server-Task " + callable); return Futures.immediateFailedFuture(exception); } } } public ListenableFuture schedule(Runnable run) { return this.callFromMainThread(Executors.callable(run)); } public boolean isMainThread() { return Thread.currentThread() == this.serverThread; } public Position getRandomSpawnPosition(WorldPos origin) { WorldServer world = this.getWorld(origin.getDimension()); world = world == null ? this.space : world; BlockPos pos = origin; int radius = SVars.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.5, (double)y, pos.getZ() + 0.5, -180.0f + world.rand.floatv() * 360.0f, 0.0f, world); } public void addUser(User user) { this.users.put(user.getUser(), user); } public void addPlayer(NetConnection connection, String loginUser) { TagObject tag = this.readPlayer(loginUser); Player conn = new Player(this, connection, loginUser); if(tag != null) conn.readTags(tag); if(SVars.compression >= 0) { connection.sendPacket(new RPacketEnableCompression(SVars.compression), new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { connection.setCompressionTreshold(SVars.compression); } }); } connection.sendPacket(new RPacketLoginSuccess()); 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); tag = conn.readCharacter(); WorldServer world = tag == null ? this.space : this.getWorld(tag.getString("Dimension")); 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); else player.onInitialSpawn(null); if(tag == null) player.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f); Log.NETWORK.info(loginUser + "[" + connection.getCutAddress() + "] hat sich mit Objekt-ID " + player.getId() + " auf " + UniverseRegistry.getName(world.dimension) + ": " + String.format("%.2f %.2f %.2f", player.posX, player.posY, player.posZ) + " verbunden (" + (tag == null ? "Charakter-Editor" : "'" + player.getCommandName() + "'") + ")"); List> vars = Lists.newArrayList(); for(Entry var : this.variables.entrySet()) { if(var.getValue().sync) vars.add(new Pair(var.getKey(), var.getValue().getRaw())); } conn.sendPacket(new SPacketServerConfig(vars)); conn.sendPacket(new SPacketJoinGame(player.getId(), world.dimension, EntityRegistry.getEntityID(player), tag == null)); conn.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem)); this.sendPacket(new SPacketPlayerListItem(false, conn)); world.spawnEntityInWorld(player); this.preparePlayer(player, null); for(int i = 0; i < this.players.size(); ++i) { Player other = this.players.get(i); conn.sendPacket(new SPacketPlayerListItem(false, other)); } 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(StatusEffect effect : player.getEffects()) { conn.sendPacket(new SPacketEntityEffect(player.getId(), effect)); } conn.sendPacket(new SPacketPlayerAbilities(player)); conn.addSelfToInternalCraftingInventory(); conn.onConnect(); } public void removePlayer(Player conn) { EntityNPC player = conn.getEntity(); player.unmount(); this.writePlayer(conn); WorldServer world = (WorldServer)player.getServerWorld(); 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); this.sendPacket(new SPacketPlayerListItem(true, conn)); } private void preparePlayer(EntityNPC player, WorldServer oldWorld) { WorldServer newWorld = (WorldServer)player.getServerWorld(); if(oldWorld != null) { oldWorld.removePlayer(player); } newWorld.addPlayer(player); newWorld.loadChunk((int)player.posX >> 4, (int)player.posZ >> 4); } private TagObject readPlayer(String user) { TagObject tag = null; try { File dat = new File(new File("players"), user + ".cdt"); if(dat.exists() && dat.isFile()) { tag = TagObject.readGZip(dat); } } catch(Exception e) { Log.IO.error(e, "Konnte Spielerdaten für " + user + " nicht laden"); } return tag; } private void writePlayer(Player conn) { try { TagObject tag = new TagObject(); EntityNPC entity = conn.getPresentEntity(); if(entity != null) { TagObject etag = new TagObject(); entity.writeTags(etag); etag.setString("Dimension", UniverseRegistry.getName(entity.worldObj.dimension)); 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); if(dat.exists()) { dat.delete(); } tmp.renameTo(dat); } catch(Exception e) { Log.IO.error(e, "Konnte Spielerdaten für " + conn.getUser() + " nicht speichern"); } } public void recreatePlayer(Player conn) { EntityNPC old = conn.getEntity(); BlockPos pos = old.getPosition(); WorldServer oldWorld = (WorldServer)old.getServerWorld(); oldWorld.removePlayerFromTrackers(old); oldWorld.untrackEntity(old); oldWorld.removePlayer(old); oldWorld.removePlayerEntityDangerously(old); WorldPos bed = old.getSpawnPoint(); WorldPos origin = old.getOrigin(); 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) { world = this.getWorld(origin.getDimension()); world = world == null ? this.space : world; } EntityNPC nplayer = conn.createPlayer(world, EntityRegistry.getEntityString(old)); conn.copyPlayer(old); nplayer.setId(old.getId()); nplayer.setOrigin(origin); 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 { Position rpos = this.getRandomSpawnPosition(origin); nplayer.setLocationAndAngles(rpos.x(), rpos.y(), rpos.z(), rpos.yaw(), rpos.pitch()); } world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4); if(bed != null ? SVars.checkBed : SVars.spawnRadius >= 0) { while(!world.getCollidingBoundingBoxes(nplayer, nplayer.getEntityBoundingBox()).isEmpty() && nplayer.posY < 512.0D) { nplayer.setPosition(nplayer.posX, nplayer.posY + 1.0D, nplayer.posZ); } } conn.sendPacket(new SPacketRespawn(world.dimension != oldWorld.dimension ? world.dimension : null, EntityRegistry.getEntityID(nplayer), conn.isInEditor())); 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); conn.addFeed(TextColor.RED + "* Bei %d, %d, %d in Dimension %s gestorben", pos.getX(), pos.getY(), pos.getZ(), old.worldObj.dimension.getFormattedName(false)); } public TagObject swapPlayer(Player conn, TagObject tag, Class clazz) { EntityNPC old = conn.getEntity(); old.unmount(); TagObject oldTag = new TagObject(); old.writeTags(oldTag); oldTag.setString("Dimension", UniverseRegistry.getName(old.worldObj.dimension)); oldTag.setString("id", EntityRegistry.getEntityString(old)); WorldServer oldWorld = (WorldServer)old.getServerWorld(); oldWorld.removePlayerFromTrackers(old); oldWorld.untrackEntity(old); oldWorld.removePlayer(old); oldWorld.removePlayerEntityDangerously(old); WorldServer world = tag == null ? this.space : this.getWorld(tag.getString("Dimension")); world = world == null ? this.space : world; EntityNPC nplayer = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(clazz) : tag.getString("id")); if(tag != null) nplayer.readTags(tag); else nplayer.onInitialSpawn(null); nplayer.setId(old.getId()); if(tag == null) nplayer.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f); world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4); world.addPlayer(nplayer); world.spawnEntityInWorld(nplayer); conn.sendPacket(new SPacketRespawn(world.dimension != oldWorld.dimension ? world.dimension : null, EntityRegistry.getEntityID(nplayer), conn.isInEditor())); 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.addSelfToInternalCraftingInventory(); nplayer.setHealth(nplayer.getHealth()); this.updateTimeAndWeatherForPlayer(conn, world); this.syncPlayerInventory(nplayer); for(StatusEffect effect : nplayer.getEffects()) { conn.sendPacket(new SPacketEntityEffect(nplayer.getId(), effect)); } conn.sendPlayerAbilities(); return oldTag; } public void transferToDimension(EntityNPC player, Dimension dimension, BlockPos pos, float yaw, float pitch, PortalType portal) { WorldServer oldWorld = (WorldServer)player.getServerWorld(); // this.getWorld(player.dimension); WorldServer newWorld = this.getWorld(dimension); player.connection.sendPacket(new SPacketRespawn(newWorld.dimension, EntityRegistry.getEntityID(player), player.connection.isInEditor())); 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); this.updateTimeAndWeatherForPlayer((Player)player.connection, newWorld); this.syncPlayerInventory(player); for(StatusEffect effect : player.getEffects()) { player.connection.sendPacket(new SPacketEntityEffect(player.getId(), effect)); } 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) { conn.sendPacket(packet); } } public void saveAllPlayerData(boolean message) { if(message) { Log.TICK.info("Speichere Spielerdaten"); } for(Player conn : this.players) { this.writePlayer(conn); } User.saveDatabase(this.users); } private void updateTimeAndWeatherForPlayer(Player conn, WorldServer world) { conn.sendPacket(new SPacketTimeUpdate(world.getDayTime(), this.getInfo())); conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.SET_WEATHER, world.getWeather().ordinal())); 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())); } public void syncPlayerInventory(EntityNPC player) { player.connection.sendContainer(player.inventoryContainer, player.inventoryContainer.getInventory()); player.connection.setPlayerHealthUpdated(); player.connection.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem)); } private void terminateEndpoint(String message) { if(this.started) for(Player conn : Lists.newArrayList(this.players)) { conn.disconnect(message); } synchronized(this.serverThread) { if(this.endpoint != null) { Log.NETWORK.info("Schließe Port"); try { this.endpoint.channel().close().sync(); this.endpoint = null; } catch(InterruptedException e) { Log.NETWORK.warn("Unterbrochen beim Schließen des Kanals"); } } } } private void networkTick() { synchronized(this.clients) { Iterator 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) { Log.NETWORK.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten"); manager.sendPacket(new SPacketDisconnect(e.getMessage()), new GenericFutureListener>() { public void operationComplete(Future future) throws Exception { manager.closeChannel("Fehlerhaftes Datenpaket"); } }); manager.disableAutoRead(); } } } } } } public Map getWarps() { return this.warps; } private void stopServer() { if(!this.stopped) { Log.SYSTEM.info("Beende Server"); this.terminateEndpoint(this.endMessage); if(this.started) { this.saveAllPlayerData(true); this.saveAllWorlds(true); Region.finishWrite(); } } } public void bind(int port) { synchronized(this.serverThread) { if(port >= 0) { try { if(this.endpoint != null) this.terminateEndpoint("Wechsele auf Port " + port); Log.NETWORK.info("Öffne Port %d auf 0.0.0.0 (Timeout %ds)", port, SVars.timeout); this.endpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer() { 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(SVars.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) { Log.NETWORK.error(e, "**** KONNTE NICHT AN PORT " + port + " ANBINDEN!"); } } else { if(this.endpoint != null) this.terminateEndpoint("Trenne Verbindung"); } } } public void shutdown(String message) { this.running = false; this.endMessage = message; } 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); } public PublicKey getPublicKey() { return this.keyPair.getPublic(); } public PrivateKey getPrivateKey() { return this.keyPair.getPrivate(); } public void log(String msg) { Log.CONSOLE.info(msg); } public Position getExecPos() { return this.execPos; } public void setExecPos(Position pos) { this.execPos = pos; } public boolean isConsole() { return true; } }