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;
|
|
|
|
|
2025-03-25 11:31:48 +01:00
|
|
|
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;
|
2025-03-11 10:26:48 +01:00
|
|
|
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;
|
2025-03-26 12:22:32 +01:00
|
|
|
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;
|
2025-03-11 10:26:48 +01:00
|
|
|
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());
|
2025-03-26 12:22:32 +01:00
|
|
|
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-26 12:22:32 +01:00
|
|
|
Player player = this.getPlayer(list[i]);
|
2025-03-11 00:23:54 +01:00
|
|
|
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) {
|
2025-03-26 12:22:32 +01:00
|
|
|
if(this.debug || !Player.isValidUser(user))
|
2025-03-11 00:23:54 +01:00
|
|
|
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) {
|
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
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
public List<Player> getPlayers() {
|
2025-03-11 00:23:54 +01:00
|
|
|
return this.players;
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
public Player getPlayer(String user) {
|
2025-03-11 00:23:54 +01:00
|
|
|
return this.usermap.get(user.toLowerCase());
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2025-03-26 12:22:32 +01:00
|
|
|
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);
|
|
|
|
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");
|
|
|
|
|
2025-03-16 23:14:24 +01:00
|
|
|
if(Config.preloadAll || (Config.preloadLocal && conn.isLocal()))
|
|
|
|
this.preload(player);
|
|
|
|
|
2025-03-11 00:23:54 +01:00
|
|
|
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) {
|
2025-03-26 12:22:32 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
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);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
private void writePlayer(Player conn) {
|
2025-03-11 00:23:54 +01:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
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();
|
|
|
|
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)
|
2025-03-26 12:22:32 +01:00
|
|
|
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-26 12:22:32 +01:00
|
|
|
public void swapPlayer(Player conn, NBTTagCompound tag) {
|
2025-03-11 00:23:54 +01:00
|
|
|
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) {
|
2025-03-26 12:22:32 +01:00
|
|
|
for(Player conn : this.players) {
|
2025-03-11 00:23:54 +01:00
|
|
|
conn.sendPacket(packet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void sendPacket(Packet packet, int dimension) {
|
2025-03-26 12:22:32 +01:00
|
|
|
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) {
|
2025-03-26 12:22:32 +01:00
|
|
|
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");
|
|
|
|
}
|
2025-03-26 12:22:32 +01:00
|
|
|
for(Player conn : this.players) {
|
2025-03-11 00:23:54 +01:00
|
|
|
this.writePlayer(conn);
|
|
|
|
}
|
|
|
|
// this.saveUsers();
|
|
|
|
}
|
|
|
|
|
2025-03-26 12:22:32 +01:00
|
|
|
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");
|
|
|
|
Log.JNI.info("Öffne Port " + port + " auf 0.0.0.0");
|
|
|
|
this.endpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<Channel>() {
|
|
|
|
protected void initChannel(Channel channel) throws Exception {
|
|
|
|
try {
|
|
|
|
channel.config().setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(true));
|
|
|
|
}
|
|
|
|
catch(ChannelException e) {
|
|
|
|
}
|
|
|
|
channel.pipeline().addLast((String)"timeout", (ChannelHandler)(new ReadTimeoutHandler(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);
|
2025-03-26 12:22:32 +01:00
|
|
|
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();
|
2025-03-26 12:22:32 +01:00
|
|
|
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() {
|
2025-03-26 12:22:32 +01:00
|
|
|
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)
|
2025-03-26 12:22:32 +01:00
|
|
|
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() {
|
2025-03-26 12:22:32 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|