tcr/java/src/game/Server.java

1207 lines
40 KiB
Java
Raw Normal View History

2025-03-11 00:23:54 +01:00
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 game.collect.Lists;
import game.collect.Maps;
import game.future.Futures;
import game.future.ListenableFuture;
import game.future.ListenableFutureTask;
import game.future.ThreadFactoryBuilder;
2025-03-16 17:40:47 +01:00
2025-03-11 00:23:54 +01:00
import game.color.TextColor;
2025-03-25 23:10:40 +01:00
import game.command.CommandEnvironment;
2025-03-11 00:23:54 +01:00
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;
2025-03-11 00:23:54 +01:00
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.HandshakeHandlerMemory;
import game.network.HandshakeHandlerTCP;
import game.network.Player;
2025-03-11 00:23:54 +01:00
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;
2025-03-11 00:23:54 +01:00
import game.world.BlockPos;
import game.world.PortalType;
import game.world.Position;
import game.world.Region;
import game.world.Region.FolderInfo;
2025-03-16 17:45:08 +01:00
import game.world.World;
import game.world.WorldPos;
import game.world.WorldServer;
2025-03-16 17:40:47 +01:00
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;
2025-03-11 00:23:54 +01:00
public final class Server implements Runnable, IThreadListener {
private static final LazyLoadBase<NioEventLoopGroup> SERVER_NIO_EVENTLOOP = new LazyLoadBase<NioEventLoopGroup>() {
protected NioEventLoopGroup load() {
return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
}
};
private final Thread serverThread;
private final List<NetConnection> clients = Collections.<NetConnection>synchronizedList(Lists.<NetConnection>newArrayList());
private final List<Player> players = Lists.<Player>newArrayList();
private final Map<String, Player> usermap = Maps.<String, Player>newHashMap();
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 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;
2025-03-16 23:14:24 +01:00
private String message;
2025-03-11 00:23:54 +01:00
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;
2025-03-16 23:14:24 +01:00
private int progress = -1;
private int total = 0;
2025-03-11 00:23:54 +01:00
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;
}
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 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);
2025-03-27 17:54:03 +01:00
// Player player = this.getPlayer(list[i]);
// if(player != null)
// list[i] = player.getUser();
2025-03-11 00:23:54 +01:00
}
}
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 || !Player.isValidUser(user))
2025-03-11 00:23:54 +01:00
return null;
NBTTagCompound tag = null;
try {
2025-03-27 17:54:03 +01:00
File dat = new File(this.playerDir, user + ".nbt");
2025-03-11 00:23:54 +01:00
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) {
2025-03-16 23:14:24 +01:00
this.startProgress(true, this.worlds.size());
2025-03-11 00:23:54 +01:00
}
int done = 0;
this.saveWorldInfo();
for(WorldServer world : this.worlds) {
if(message) {
2025-03-16 23:14:24 +01:00
this.setProgress(done);
2025-03-11 00:23:54 +01:00
}
++done;
world.saveAllChunks();
}
2025-03-16 23:14:24 +01:00
this.setProgress(-1);
2025-03-11 00:23:54 +01:00
}
2025-03-16 23:14:24 +01:00
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()));
2025-03-11 00:23:54 +01:00
}
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) {
2025-03-16 23:14:24 +01:00
this.setMessage("Welt wird erstellt und geladen");
2025-03-11 00:23:54 +01:00
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");
}
}
2025-03-16 23:14:24 +01:00
2025-03-28 14:30:37 +01: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;
this.setMessage("Landschaft wird generiert");
this.startProgress(false, total);
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();
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);
}
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))
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<WorldServer> 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<String> getAllUsernames() {
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<Player> getPlayers() {
2025-03-11 00:23:54 +01:00
return this.players;
}
public Player getPlayer(String user) {
2025-03-27 17:54:03 +01:00
return this.usermap.get(user);
2025-03-11 00:23:54 +01:00
}
2025-03-16 23:14:24 +01:00
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;
}
}
2025-03-11 00:23:54 +01:00
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 <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) {
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());
}
2025-03-11 00:23:54 +01:00
public String addPlayer(NetConnection connection, String loginUser, String loginPass) {
NBTTagCompound tag = this.readPlayer(loginUser);
Player conn = new Player(this, connection, loginUser);
2025-03-11 00:23:54 +01:00
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);
2025-03-27 17:54:03 +01:00
this.usermap.put(loginUser, conn);
2025-03-11 00:23:54 +01:00
2025-03-27 17:54:03 +01:00
tag = conn.readCharacter();
WorldServer world = tag == null ? this.space : this.getWorld(tag.getInteger("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.readFromNBT(tag);
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
Log.JNI.info(loginUser + "[" + connection.getCutAddress() + "] hat sich mit Objekt-ID "
+ 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-28 14:30:37 +01:00
if(Config.preloadLocal && conn.isLocal() && this.players.size() == 1)
this.preload(world, (int)player.posX, (int)player.posZ);
2025-03-16 23:14:24 +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();
this.sendPacket(new S38PacketPlayerListItem(false, conn));
world.spawnEntityInWorld(player);
this.preparePlayer(player, null);
for(int i = 0; i < this.players.size(); ++i) {
Player other = this.players.get(i);
2025-03-11 00:23:54 +01:00
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(Player conn) {
2025-03-11 00:23:54 +01:00
EntityNPC player = conn.getEntity();
player.unmount();
this.writePlayer(conn);
WorldServer world = player.getServerWorld();
world.removeEntity(player);
world.removePlayer(player);
this.players.remove(conn);
2025-03-27 17:54:03 +01:00
this.usermap.remove(conn.getUser());
2025-03-11 00:23:54 +01:00
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 {
2025-03-27 17:54:03 +01:00
File dat = new File(this.playerDir, user + ".nbt");
2025-03-11 00:23:54 +01:00
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(Player conn) {
2025-03-11 00:23:54 +01:00
if(this.debug)
return;
try {
NBTTagCompound tag = new NBTTagCompound();
2025-03-27 17:54:03 +01:00
EntityNPC entity = conn.getPresentEntity();
if(entity != null) {
NBTTagCompound etag = new NBTTagCompound();
entity.writeToNBT(etag);
etag.setInteger("Dimension", entity.worldObj.dimension.getDimensionId());
etag.setString("id", EntityRegistry.getEntityString(entity));
conn.writeCharacter(etag);
}
// else
// etag = new NBTTagCompound();
2025-03-11 00:23:54 +01:00
conn.writeToNBT(tag);
2025-03-27 17:54:03 +01:00
File tmp = new File(this.playerDir, conn.getUser() + ".nbt.tmp");
File dat = new File(this.playerDir, conn.getUser() + ".nbt");
2025-03-11 00:23:54 +01:00
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(Player conn) {
2025-03-11 00:23:54 +01:00
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();
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);
// 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-03-27 17:54:03 +01:00
public NBTTagCompound swapPlayer(Player conn, NBTTagCompound tag, Class<? extends EntityNPC> clazz) {
2025-03-11 00:23:54 +01:00
EntityNPC old = conn.getEntity();
old.unmount();
2025-03-27 17:54:03 +01:00
NBTTagCompound oldTag = new NBTTagCompound();
2025-03-11 00:23:54 +01:00
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;
2025-03-27 17:54:03 +01:00
WorldServer world = tag == null ? this.space : this.getWorld(tag.getInteger("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.readFromNBT(tag);
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()) {
conn.sendPacket(new S1DPacketEntityEffect(nplayer.getId(), effect));
}
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) {
WorldServer oldWorld = player.getServerWorld(); // this.getWorld(player.dimension);
// 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);
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(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) {
Log.JNI.info("Speichere Spielerdaten");
}
for(Player conn : this.players) {
2025-03-11 00:23:54 +01:00
this.writePlayer(conn);
}
// this.saveUsers();
}
private void updateTimeAndWeatherForPlayer(Player conn, WorldServer world) {
2025-03-11 00:23:54 +01:00
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");
2025-03-28 14:30:37 +01:00
Log.JNI.info("Öffne Port %d auf 0.0.0.0 (Timeout %ds)", port, Config.timeout);
2025-03-11 00:23:54 +01: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) {
}
2025-03-28 14:30:37 +01:00
channel.pipeline().addLast((String)"timeout", (ChannelHandler)(new ReadTimeoutHandler(Config.timeout)))
2025-03-11 00:23:54 +01:00
.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 HandshakeHandlerTCP(Server.this, manager));
2025-03-11 00:23:54 +01:00
}
}).group(SERVER_NIO_EVENTLOOP.getValue()).localAddress((InetAddress)null, port)).bind().syncUninterruptibly();
}
}
public SocketAddress setLocalEndpoint() {
// ChannelFuture future;
synchronized(this.serverThread) {
if(this.localEndpoint == null) {
// throw new IllegalStateException("Lokaler Eingangspunkt bereits gesetzt");
// }
this.localEndpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(LocalServerChannel.class)).childHandler(new ChannelInitializer<Channel>() {
protected void initChannel(Channel channel) throws Exception {
NetConnection manager = new NetConnection();
manager.setNetHandler(new HandshakeHandlerMemory(Server.this, manager));
2025-03-11 00:23:54 +01:00
Server.this.clients.add(manager);
channel.pipeline().addLast((String)"packet_handler", (ChannelHandler)manager);
}
}).group((EventLoopGroup)SERVER_NIO_EVENTLOOP.getValue()).localAddress(LocalAddress.ANY)).bind().syncUninterruptibly();
// this.localEndpoint = future;
}
}
return this.localEndpoint.channel().localAddress();
}
private void unsetLanEndpoint() {
for(Player conn : Lists.newArrayList(this.players)) {
2025-03-11 00:23:54 +01:00
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<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) {
if(manager.isLocalChannel()) {
throw e;
}
Log.JNI.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten");
manager.sendPacket(new SPacketDisconnect(), new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) throws Exception {
manager.closeChannel("Fehlerhaftes Datenpaket");
}
});
manager.disableAutoRead();
}
}
}
}
}
}
public Map<String, Position> getWarps() {
return this.warps;
}
public void start() {
this.serverThread.start();
}
void stopServer() {
if(!this.stopped) {
2025-03-16 23:14:24 +01:00
this.setProgress(-1);
this.setMessage("Stoppe server");
2025-03-11 00:23:54 +01:00
Log.JNI.info("Beende Server");
if(this.started)
for(Player conn : Lists.newArrayList(this.players)) {
2025-03-11 00:23:54 +01:00
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(Player conn : Lists.newArrayList(Server.this.players)) { // = Server.this.getPlayer(Server.this.owner);
2025-03-11 00:23:54 +01:00
// if(conn != null)
if(conn.isLocal())
Server.this.removePlayer(conn);
}
}
}));
this.running = false;
}
}