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; import java.util.Iterator; import java.util.List; import java.util.Map; 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 com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; import com.google.common.util.concurrent.ThreadFactoryBuilder; import game.color.TextColor; import game.command.ScriptEnvironment; import game.dimension.Dimension; import game.dimension.Space; import game.entity.Entity; import game.entity.npc.EntityHuman; import game.entity.npc.EntityNPC; import game.init.Config; import game.init.EntityRegistry; import game.init.UniverseRegistry; import game.log.Log; import game.nbt.NBTLoader; import game.nbt.NBTTagCompound; import game.nbt.NBTTagList; import game.network.IThreadListener; import game.network.LazyLoadBase; import game.network.NetConnection; import game.network.NetHandler.ThreadQuickExitException; import game.network.NetHandlerHandshakeMemory; import game.network.NetHandlerHandshakeTCP; import game.network.NetHandlerPlayServer; import game.network.Packet; import game.network.PacketDecoder; import game.network.PacketEncoder; import game.network.PacketPrepender; import game.network.PacketSplitter; import game.packet.RPacketEnableCompression; import game.packet.RPacketLoginSuccess; import game.packet.S1DPacketEntityEffect; import game.packet.S2BPacketChangeGameState; import game.packet.S38PacketPlayerListItem; import game.packet.S39PacketPlayerAbilities; import game.packet.SPacketDisconnect; import game.packet.SPacketHeldItemChange; import game.packet.SPacketJoinGame; import game.packet.SPacketRespawn; import game.packet.SPacketSetExperience; import game.packet.SPacketSkin; import game.packet.SPacketTimeUpdate; import game.packet.SPacketWorld; import game.potion.PotionEffect; import game.util.ExtMath; import game.world.BlockPos; import game.world.PortalType; import game.world.Position; import game.world.Region; import game.world.Region.FolderInfo; import game.world.World; import game.world.WorldPos; import game.world.WorldServer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelException; import io.netty.channel.ChannelFuture; 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 { private static final LazyLoadBase SERVER_NIO_EVENTLOOP = new LazyLoadBase() { protected NioEventLoopGroup load() { return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); } }; private final Thread serverThread; private final List clients = Collections.synchronizedList(Lists.newArrayList()); private final List players = Lists.newArrayList(); private final Map usermap = Maps.newHashMap(); 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 ScriptEnvironment scriptEnv = new ScriptEnvironment(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; private boolean started; 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; private int progress = -1; private int total = 0; 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; } public ScriptEnvironment getScriptEnvironment() { return this.scriptEnv; } public boolean isStarted() { return this.started; } public String[] getUsers() { String[] list = this.debug ? null : this.playerDir.list(); if(list == null) { list = new String[0]; } for(int i = 0; i < list.length; ++i) { if(list[i].endsWith(".nbt")) { list[i] = list[i].substring(0, list[i].length() - 4); NetHandlerPlayServer player = this.getPlayer(list[i]); if(player != null) list[i] = player.getUser(); } } return list; } public void setLocalUser(String user) { this.localUser = user; } public void saveWorldInfo() { if(!this.debug) { Region.saveWorldInfo(this.baseDir, this.space.getDayTime(), this.localUser); WorldServer.saveWarps(this.baseDir, this.warps); } } public Position getOfflinePosition(String user) { if(this.debug || !NetHandlerPlayServer.isValidUser(user)) return null; NBTTagCompound tag = null; try { File dat = new File(this.playerDir, user.toLowerCase() + ".nbt"); if(dat.exists() && dat.isFile()) { tag = NBTLoader.readGZip(dat); } } catch(Exception e) { Log.JNI.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht laden"); } if(tag == null) return null; NBTTagList pos = tag.getTagList("Pos", 6); NBTTagList rot = tag.getTagList("Rotation", 5); double posX = pos.getDoubleAt(0); double posY = pos.getDoubleAt(1); double posZ = pos.getDoubleAt(2); float rotYaw = rot.getFloatAt(0); float rotPitch = rot.getFloatAt(1); int dimension = tag.getInteger("Dimension"); return new Position(posX, posY, posZ, rotYaw, rotPitch, dimension); } private void saveAllWorlds(boolean message) { if(this.debug) return; if(message) { this.startProgress(true, this.worlds.size()); } int done = 0; this.saveWorldInfo(); for(WorldServer world : this.worlds) { if(message) { this.setProgress(done); } ++done; world.saveAllChunks(); } this.setProgress(-1); } 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())); } 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()); } } public void run() { long time = System.currentTimeMillis(); Log.JNI.info("Starte Server Version " + Config.VERSION); if(!this.debug) { this.setMessage("Welt wird erstellt und geladen"); FolderInfo info = Region.loadWorldInfo(this.baseDir); // 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, Space.INSTANCE, false)); this.dimensions.put(this.space.dimension.getDimensionId(), this.space); this.playerDir.mkdirs(); // 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 { Config.clear(); UniverseRegistry.clear(); Config.set("daylightCycle", "false", null); 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, 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)) { this.getWorld(dim.getDimensionId()).loadForcedChunks(); } WorldServer.loadWarps(this.baseDir, dim, this.warps); } // this.openLAN(); } Log.JNI.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.JNI.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.JNI.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.JNI.error(e, "Fehler beim Beenden des Servers"); } finally { this.stopped = true; Log.JNI.info("Server wurde beendet"); } } private void preload(Entity entity) { int done = 0; int total = Config.distance * 2 + 1; total *= total; this.setMessage("Landschaft wird generiert"); this.startProgress(false, total); WorldServer world = this.getWorld(Config.spawnDim); world = world == null ? this.space : world; int bx = (int)entity.posX >> 4; int bz = (int)entity.posZ >> 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++) { long time = System.currentTimeMillis(); if(time - last >= 10L) { this.setProgress(done); if(time - last > 1000L) { Log.JNI.info("Bereite Spawnbereich vor" + ": " + (done * 100 / total) + "%"); last = time; } } ++done; world.loadChunk(bx + x, bz + z); } } this.setProgress(-1); } 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)) Log.JNI.error(e1, "Fehler beim Ausführen von Server-Task " + task); // throw new RuntimeException(e1); } catch(InterruptedException e2) { Log.JNI.error(e2, "Fehler beim Ausführen von Server-Task " + task); } } } if(++this.syncTimer == 20) { this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime())); 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 S38PacketPlayerListItem(this.players)); this.pingTimer = 0; } if(Config.saveInterval > 0 && ++this.saveTimer >= Config.saveInterval) { this.saveAllPlayerData(false); this.saveAllWorlds(false); this.saveTimer = 0; } 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; world = new WorldServer(this, this.baseDir, this.space.getDayTime(), 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 getWorlds() { return this.worlds; } public boolean isStopped() { return this.stopped; } 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 getAllUsernames() { 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 getPlayers() { return this.players; } public NetHandlerPlayServer getPlayer(String user) { return this.usermap.get(user.toLowerCase()); } 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; } } private void setProgress(int progress) { synchronized(this) { this.progress = progress; } } private void setTotal(int total) { synchronized(this) { this.total = total; } } public void setVar(String cv, String value) { this.schedule(new Runnable() { public void run() { Config.set(cv, value, Server.this); } }); } public File getFolder() { return this.baseDir; } 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) { return Futures.immediateFailedFuture(exception); } } } public ListenableFuture schedule(Runnable run) { return this.callFromMainThread(Executors.callable(run)); } public boolean isMainThread() { return Thread.currentThread() == this.serverThread; } 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); } } public String addPlayer(NetConnection connection, String loginUser, String loginPass) { NBTTagCompound tag = this.readPlayer(loginUser); NetHandlerPlayServer conn = new NetHandlerPlayServer(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); } }); } } connection.sendPacket(new RPacketLoginSuccess()); connection.setNetHandler(conn); this.players.add(conn); this.usermap.put(loginUser.toLowerCase(), conn); tag = conn.getSelectedCharacter(false); WorldServer world = this.getWorld(tag == null ? Config.spawnDim : tag.getInteger("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.readFromNBT(tag); else player.onInitialSpawn(null); // player.setWorld(world); if(tag == null) this.movePlayerToSpawn(player); Log.JNI.info(loginUser + "[" + connection.getCutAddress() + "] hat sich mit Objekt-ID " + player.getId() + " auf Level " + world.dimension.getDimensionId() + ": " + String.format("%.2f %.2f %.2f", player.posX, player.posY, player.posZ) + " verbunden"); if(Config.preloadAll || (Config.preloadLocal && conn.isLocal())) this.preload(player); conn.sendPacket(new SPacketJoinGame(player.getId(), world.dimension, EntityRegistry.getEntityID(player))); conn.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem)); conn.sendPacket(new SPacketWorld(WorldServer.clampGravity(), Config.dayCycle, Config.timeFlow)); // conn.initializeStats(); this.sendPacket(new S38PacketPlayerListItem(false, conn)); world.spawnEntityInWorld(player); this.preparePlayer(player, null); for(int i = 0; i < this.players.size(); ++i) { NetHandlerPlayServer other = this.players.get(i); conn.sendPacket(new S38PacketPlayerListItem(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(PotionEffect effect : player.getEffects()) { conn.sendPacket(new S1DPacketEntityEffect(player.getId(), effect)); } conn.sendPacket(new S39PacketPlayerAbilities(player)); conn.addSelfToInternalCraftingInventory(); conn.onConnect(); return null; } public void removePlayer(NetHandlerPlayServer conn) { EntityNPC player = conn.getEntity(); player.unmount(); this.writePlayer(conn); WorldServer world = player.getServerWorld(); world.removeEntity(player); world.removePlayer(player); this.players.remove(conn); this.usermap.remove(conn.getUser().toLowerCase()); this.sendPacket(new S38PacketPlayerListItem(true, conn)); } private void preparePlayer(EntityNPC player, WorldServer oldWorld) { WorldServer newWorld = player.getServerWorld(); if(oldWorld != null) { oldWorld.removePlayer(player); } newWorld.addPlayer(player); newWorld.loadChunk((int)player.posX >> 4, (int)player.posZ >> 4); } private NBTTagCompound readPlayer(String user) { if(this.debug) return null; NBTTagCompound tag = null; try { File dat = new File(this.playerDir, user.toLowerCase() + ".nbt"); if(dat.exists() && dat.isFile()) { tag = NBTLoader.readGZip(dat); } } catch(Exception e) { Log.JNI.error(e, "Konnte Spielerdaten für " + user + " nicht laden"); } return tag; } private void writePlayer(NetHandlerPlayServer conn) { if(this.debug) return; try { NBTTagCompound tag = new NBTTagCompound(); NBTTagCompound etag = conn.getSelectedCharacter(true); conn.getEntity().writeToNBT(etag); etag.setInteger("Dimension", conn.getEntity().worldObj.dimension.getDimensionId()); etag.setString("id", EntityRegistry.getEntityString(conn.getEntity())); conn.writeToNBT(tag); File tmp = new File(this.playerDir, conn.getUser().toLowerCase() + ".nbt.tmp"); File dat = new File(this.playerDir, conn.getUser().toLowerCase() + ".nbt"); NBTLoader.writeGZip(tag, tmp); if(dat.exists()) { dat.delete(); } tmp.renameTo(dat); } catch(Exception e) { Log.JNI.error(e, "Konnte Spielerdaten für " + conn.getUser() + " nicht speichern"); } } public void recreatePlayer(NetHandlerPlayServer conn) { EntityNPC old = conn.getEntity(); BlockPos pos = old.getPosition(); old.getServerWorld().removePlayerFromTrackers(old); old.getServerWorld().untrackEntity(old); old.getServerWorld().removePlayer(old); old.getServerWorld().removePlayerEntityDangerously(old); WorldPos bed = old.getSpawnPoint(); 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(Config.spawnDim); world = world == null ? this.space : world; } EntityNPC nplayer = conn.createPlayer(world, EntityRegistry.getEntityString(old)); conn.clonePlayer(old); nplayer.setId(old.getId()); 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 { this.movePlayerToSpawn(nplayer); } 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); } } conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer))); 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.addFeed(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 void swapPlayer(NetHandlerPlayServer conn, NBTTagCompound tag) { EntityNPC old = conn.getEntity(); old.unmount(); NBTTagCompound oldTag = conn.getSelectedCharacter(true); old.writeToNBT(oldTag); oldTag.setInteger("Dimension", old.worldObj.dimension.getDimensionId()); oldTag.setString("id", EntityRegistry.getEntityString(old)); old.getServerWorld().removePlayerFromTrackers(old); old.getServerWorld().untrackEntity(old); old.getServerWorld().removePlayer(old); old.getServerWorld().removePlayerEntityDangerously(old); // old.dead = false; WorldServer world = this.getWorld(tag == null ? Config.spawnDim : tag.getInteger("Dimension")); world = world == null ? this.space : world; EntityNPC nplayer = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(EntityHuman.class) : tag.getString("id")); // conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer))); if(tag != null) nplayer.readFromNBT(tag); else nplayer.onInitialSpawn(null); // nplayer.clonePlayer(old); nplayer.setId(old.getId()); if(tag == null) this.movePlayerToSpawn(nplayer); world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4); world.addPlayer(nplayer); world.spawnEntityInWorld(nplayer); conn.sendPacket(new SPacketRespawn(world.dimension, EntityRegistry.getEntityID(nplayer))); 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()) { conn.sendPacket(new S1DPacketEntityEffect(nplayer.getId(), effect)); } conn.sendPlayerAbilities(); } public void transferToDimension(EntityNPC player, int dimension, BlockPos pos, float yaw, float pitch, PortalType portal) { WorldServer oldWorld = player.getServerWorld(); // this.getWorld(player.dimension); // player.dimension = dimension; WorldServer newWorld = this.getWorld(dimension); player.connection.sendPacket(new SPacketRespawn(newWorld.dimension, EntityRegistry.getEntityID(player))); 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); this.updateTimeAndWeatherForPlayer(player.connection, newWorld); this.syncPlayerInventory(player); for(PotionEffect effect : player.getEffects()) { player.connection.sendPacket(new S1DPacketEntityEffect(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(NetHandlerPlayServer conn : this.players) { conn.sendPacket(packet); } } public void sendPacket(Packet packet, int dimension) { for(NetHandlerPlayServer conn : this.players) { 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(NetHandlerPlayServer conn : this.players) { 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) { Log.JNI.info("Speichere Spielerdaten"); } for(NetHandlerPlayServer conn : this.players) { this.writePlayer(conn); } // this.saveUsers(); } private void updateTimeAndWeatherForPlayer(NetHandlerPlayServer conn, WorldServer world) { conn.sendPacket(new SPacketTimeUpdate(world.getDayTime())); 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())); conn.sendPacket(new S2BPacketChangeGameState(S2BPacketChangeGameState.Action.FOG_STRENGTH, world.getFogStrength())); conn.sendPacket(new S2BPacketChangeGameState(S2BPacketChangeGameState.Action.TEMPERATURE, world.getTempOffset())); } public void syncPlayerInventory(EntityNPC player) { player.connection.sendContainerToPlayer(player.inventoryContainer); player.connection.setPlayerHealthUpdated(); player.connection.sendPacket(new SPacketHeldItemChange(player.inventory.currentItem)); } private void setLanEndpoint(int port) throws IOException { synchronized(this.serverThread) { if(this.endpoint != null) this.unsetLanEndpoint(); // throw new IllegalStateException("Eingangspunkt bereits gesetzt"); Log.JNI.info("Öffne Port " + port + " auf 0.0.0.0"); 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(30))) .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 NetHandlerHandshakeTCP(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() { protected void initChannel(Channel channel) throws Exception { NetConnection manager = new NetConnection(); manager.setNetHandler(new NetHandlerHandshakeMemory(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(NetHandlerPlayServer conn : Lists.newArrayList(this.players)) { if(!conn.isLocal()) conn.disconnect(); } this.terminateEndpoints(false); } private void terminateEndpoints(boolean local) { synchronized(this.serverThread) { if(this.endpoint != null) { Log.JNI.info("Schließe Port"); try { this.endpoint.channel().close().sync(); this.endpoint = null; } catch(InterruptedException e) { 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"); } } } } 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) { if(manager.isLocalChannel()) { throw e; } Log.JNI.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten"); manager.sendPacket(new SPacketDisconnect(), new GenericFutureListener>() { public void operationComplete(Future future) throws Exception { manager.closeChannel("Fehlerhaftes Datenpaket"); } }); manager.disableAutoRead(); } } } } } } public Map getWarps() { return this.warps; } public void start() { this.serverThread.start(); } void stopServer() { if(!this.stopped) { this.setProgress(-1); this.setMessage("Stoppe server"); Log.JNI.info("Beende Server"); if(this.started) for(NetHandlerPlayServer conn : Lists.newArrayList(this.players)) { conn.disconnect(); } this.terminateEndpoints(true); if(this.started) { Log.JNI.info("Speichere Spieler"); this.saveAllPlayerData(true); Log.JNI.info("Speichere Welt"); this.saveAllWorlds(true); Region.finishWrite(); } } } public void bind(int port) { this.schedule(new Runnable() { public void run() { if(port >= 0) { try { Server.this.setLanEndpoint(port); } catch(IOException e) { Log.JNI.error(e, "**** KONNTE NICHT AN PORT " + port + " ANBINDEN!"); } } else { Server.this.unsetLanEndpoint(); } } }); } public void shutdown() { Futures.getUnchecked(this.schedule(new Runnable() { public void run() { for(NetHandlerPlayServer conn : Lists.newArrayList(Server.this.players)) { // = Server.this.getPlayer(Server.this.owner); // if(conn != null) if(conn.isLocal()) Server.this.removePlayer(conn); } } })); this.running = false; } }