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

1433 lines
49 KiB
Java
Executable file

package server;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import common.Version;
import common.collect.Lists;
import common.collect.Maps;
import common.dimension.Area;
import common.dimension.DimType;
import common.dimension.Dimension;
import common.dimension.Moon;
import common.dimension.Section;
import common.dimension.Planet;
import common.dimension.Semi;
import common.dimension.Space;
import common.dimension.Star;
import common.effect.StatusEffect;
import common.entity.Entity;
import common.entity.npc.EntityHuman;
import common.entity.npc.EntityNPC;
import common.init.EntityRegistry;
import common.init.Registry;
import common.log.Log;
import common.net.bootstrap.ServerBootstrap;
import common.net.channel.Channel;
import common.net.channel.ChannelException;
import common.net.channel.ChannelFuture;
import common.net.channel.ChannelFutureListener;
import common.net.channel.ChannelHandler;
import common.net.channel.ChannelInitializer;
import common.net.channel.ChannelOption;
import common.net.channel.nio.NioEventLoopGroup;
import common.net.channel.socket.nio.NioServerSocketChannel;
import common.net.handler.timeout.ReadTimeoutHandler;
import common.net.util.concurrent.Future;
import common.net.util.concurrent.GenericFutureListener;
import common.network.IPlayer;
import common.network.IThreadListener;
import common.network.NetConnection;
import common.network.Packet;
import common.network.PacketDecoder;
import common.network.PacketEncoder;
import common.network.PacketPrepender;
import common.network.PacketSplitter;
import common.network.NetHandler.ThreadQuickExitException;
import common.packet.RPacketEnableCompression;
import common.packet.RPacketLoginSuccess;
import common.packet.SPacketEntityEffect;
import common.packet.SPacketChangeGameState;
import common.packet.SPacketDimensions;
import common.packet.SPacketPlayerListItem;
import common.packet.SPacketPlayerAbilities;
import common.packet.SPacketDisconnect;
import common.packet.SPacketHeldItemChange;
import common.packet.SPacketJoinGame;
import common.packet.SPacketRespawn;
import common.packet.SPacketServerConfig;
import common.packet.SPacketSetExperience;
import common.packet.SPacketSkin;
import common.packet.SPacketTimeUpdate;
import common.rng.Random;
import common.tags.TagObject;
import common.util.BlockPos;
import common.util.EncryptUtil;
import common.util.ExtMath;
import common.util.Pair;
import common.util.PortalType;
import common.util.Position;
import common.util.Color;
import common.util.Triplet;
import common.util.Util;
import common.util.Var;
import common.util.WorldPos;
import common.vars.Vars;
import common.world.World;
import server.clipboard.ReorderRegistry;
import server.clipboard.RotationRegistry;
import server.command.CommandEnvironment;
import server.command.Executor;
import server.dimension.Dimensions;
import server.dimension.Domain;
import server.dimension.Galaxy;
import server.dimension.Sector;
import server.init.TeleportRegistry;
import server.init.UniverseRegistry;
import server.network.HandshakeHandler;
import server.network.Player;
import server.network.User;
import server.vars.SVar;
import server.vars.SVars;
import server.world.Region;
import server.world.WorldServer;
public final class Server implements IThreadListener, Executor {
private final Thread thread = Thread.currentThread();
private final NioEventLoopGroup eventGroup = new NioEventLoopGroup(0, Util.getThreadFactory("Netty Server IO"));
private final Map<String, SVar> variables = Maps.newTreeMap();
private final Map<String, Map<String, Object>> overrides = Maps.newTreeMap();
private final List<NetConnection> clients = Collections.<NetConnection>synchronizedList(Lists.<NetConnection>newArrayList());
private final List<Player> players = Lists.<Player>newArrayList();
private final Map<String, Player> online = Maps.<String, Player>newHashMap();
private final Map<String, User> users = Maps.newTreeMap();
private final Queue<Runnable> queue = new ArrayDeque<Runnable>();
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();
private final CommandEnvironment scriptEnv = new CommandEnvironment(this);
private KeyPair keyPair;
private WorldServer space;
private ChannelFuture endpoint;
private Position execPos;
private boolean running = true;
private boolean stopped;
private boolean started;
private String endMessage = "Server beendet";
private long currentTime = System.nanoTime() / 1000L;
private long tpsTarget;
private long ticksDone;
private long ticksTodo;
private long timePassed;
private long lastSchedule;
private long lastPoll;
private long tpsRate;
private long lastWarning;
private int saveTimer;
private int pingTimer;
private int syncTimer;
private int perfTimer;
public static void main(String[] args) {
long time = System.currentTimeMillis();
Util.checkPlatform();
Thread.currentThread().setName("Server thread");
Locale.setDefault(Locale.ROOT);
Util.setupHandlers();
Log.init();
Log.SYSTEM.info("Java " + System.getProperty("java.version"));
Log.SYSTEM.info("Starte " + Version.NAME + " Server " + Util.VERSION + " (Protokoll #" + Util.PROTOCOL + ")");
if(Util.DEVMODE)
Log.SYSTEM.warn("Entwicklungsmodus aktiv - Debugging-Features sind verfügbar");
Registry.register();
UniverseRegistry.register();
TeleportRegistry.register();
RotationRegistry.register();
ReorderRegistry.register();
final Server server = new Server();
Util.addShutdownHook(new Runnable() {
public void run() {
server.stopServer();
Log.flushLog();
}
});
Log.setSync(server);
server.run(time);
Region.killIO();
Log.flushLog();
}
private static void readDimensions(TagObject tag) {
List<Pair<String, String>> galaxies = Lists.newArrayList();
List<Triplet<String, String, String>> sectors = Lists.newArrayList();
List<Triplet<String, String, String>> stars = Lists.newArrayList();
List<Triplet<String, String, String>> planets = Lists.newArrayList();
List<Triplet<String, String, String>> moons = Lists.newArrayList();
List<Pair<String, String>> semis = Lists.newArrayList();
List<Pair<String, String>> domains = Lists.newArrayList();
List<Triplet<String, String, String>> areas = Lists.newArrayList();
List<TagObject> list = tag.getList("Dimensions");
for(TagObject data : list) {
String name = data.getString("Name");
String type = data.getString("Type");
String display = data.hasString("CustomName") ? data.getString("CustomName") : name;
if(type.equals("galaxy"))
galaxies.add(new Pair(name, display));
else if(type.equals("sector"))
sectors.add(new Triplet(name, display, data.getString("Galaxy")));
else if(type.equals("domain"))
domains.add(new Pair(name, display));
else {
DimType dim = DimType.getByName(type);
if(dim != null) {
switch(dim) {
case SEMI:
semis.add(new Pair(name, display));
break;
case AREA:
areas.add(new Triplet(name, display, data.getString("Domain")));
break;
case MOON:
moons.add(new Triplet(name, display, data.getString("Planet")));
break;
case PLANET:
planets.add(new Triplet(name, display, data.getString("Star")));
break;
case STAR:
stars.add(new Triplet(name, display, data.getString("Sector")));
break;
}
}
}
}
for(Pair<String, String> galaxy : galaxies) {
try {
UniverseRegistry.registerCustomGalaxy(galaxy.first(), galaxy.second());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Galaxie %s nicht hinzufügen", galaxy.first());
}
}
for(Triplet<String, String, String> sector : sectors) {
try {
UniverseRegistry.registerCustomSector(sector.first(), sector.second(), sector.third());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Sektor %s nicht hinzufügen", sector.first());
}
}
for(Triplet<String, String, String> star : stars) {
try {
UniverseRegistry.registerCustomStar(star.first(), star.second(), (Star)Dimension.create(DimType.STAR), star.third());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Stern %s nicht hinzufügen", star.first());
}
}
for(Triplet<String, String, String> planet : planets) {
try {
UniverseRegistry.registerCustomPlanet(planet.first(), planet.second(), (Planet)Dimension.create(DimType.PLANET), planet.third());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Planet %s nicht hinzufügen", planet.first());
}
}
for(Triplet<String, String, String> moon : moons) {
try {
UniverseRegistry.registerCustomMoon(moon.first(), moon.second(), (Moon)Dimension.create(DimType.MOON), moon.third());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Mond %s nicht hinzufügen", moon.first());
}
}
for(Pair<String, String> semi : semis) {
try {
UniverseRegistry.registerCustomSemi(semi.first(), semi.second(), (Semi)Dimension.create(DimType.SEMI));
}
catch(Exception e) {
Log.IO.error(e, "Konnte Dimension %s nicht hinzufügen", semi.first());
}
}
for(Pair<String, String> domain : domains) {
try {
UniverseRegistry.registerCustomDomain(domain.first(), domain.second());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Bereich %s nicht hinzufügen", domain.first());
}
}
for(Triplet<String, String, String> area : areas) {
try {
UniverseRegistry.registerCustomArea(area.first(), area.second(), (Area)Dimension.create(DimType.AREA), area.third());
}
catch(Exception e) {
Log.IO.error(e, "Konnte Dimension %s nicht hinzufügen", area.first());
}
}
}
private static void writeDimensions(TagObject tag) {
List<TagObject> list = Lists.newArrayList();
for(Section obj : UniverseRegistry.getSections()) {
if(obj.isCustom()) {
TagObject data = new TagObject();
data.setString("Name", UniverseRegistry.getName(obj));
data.setString("CustomName", obj.getDisplay());
if(obj instanceof Dimension dim) {
data.setString("Type", dim.getType().getName());
switch(dim.getType()) {
case AREA:
data.setString("Domain", UniverseRegistry.getName(UniverseRegistry.getDomain((Area)obj)));
break;
case MOON:
data.setString("Planet", UniverseRegistry.getName(UniverseRegistry.getPlanet((Moon)obj)));
break;
case PLANET:
data.setString("Star", UniverseRegistry.getName(UniverseRegistry.getStar((Planet)obj)));
break;
case STAR:
data.setString("Sector", UniverseRegistry.getName(UniverseRegistry.getSector((Star)obj)));
break;
}
}
else if(obj instanceof Sector sector) {
data.setString("Type", "sector");
data.setString("Galaxy", UniverseRegistry.getName(UniverseRegistry.getGalaxy(sector)));
}
else if(obj instanceof Galaxy galaxy) {
data.setString("Type", "galaxy");
}
else if(obj instanceof Domain domain) {
data.setString("Type", "domain");
}
list.add(data);
}
}
if(!list.isEmpty())
tag.setList("Dimensions", list);
}
private void saveServerConfig(long time) {
TagObject tag = new TagObject();
tag.setLong("Time", time);
tag.setLong("LastAccess", System.currentTimeMillis());
tag.setString("Version", Util.VERSION);
TagObject cfg = new TagObject();
for(String cvar : this.variables.keySet()) {
SVar sv = this.variables.get(cvar);
String value = this.getVariableOverrideValue(cvar);
if(!sv.noDef || !sv.def.equals(value))
cfg.setString(cvar, value);
}
tag.setObject("Config", cfg);
writeDimensions(tag);
if(this.keyPair != null) {
tag.setByteArray("PrivateKey", this.keyPair.getPrivate().getEncoded());
tag.setByteArray("PublicKey", this.keyPair.getPublic().getEncoded());
}
File nfile = new File("server.cdt.tmp");
File lfile = new File("server.cdt");
try {
TagObject.writeGZip(tag, nfile);
if(lfile.exists())
lfile.delete();
nfile.renameTo(lfile);
}
catch(Exception e) {
Log.IO.error(e, "Fehler beim Schreiben von " + nfile);
}
}
private long loadServerConfig() {
File file = new File("server.cdt");
if(!file.exists())
file = new File("server.cdt.tmp");
if(file.exists()) {
try {
TagObject tag = TagObject.readGZip(file);
TagObject cfg = tag.getObject("Config");
for(String key : cfg.keySet()) {
SVar svar = this.variables.get(key);
if(svar != null)
svar.set(cfg.getString(key), false, false);
}
readDimensions(tag);
long lastPlayed = tag.getLong("LastAccess");
String version = tag.hasString("Version") ? tag.getString("Version") : null;
version = version != null && version.isEmpty() ? "<unbekannt>" : version;
long time = tag.hasLong("Time") ? tag.getLong("Time") : World.START_TIME;
Log.IO.info("Config-Version: %s", version);
Log.IO.info("Weltzeit: %d Ticks / %d Sekunden", time, time / 20L);
Log.IO.info("Zuletzt geladen: %s", new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date(lastPlayed)));
if(System.getProperty("server.regenkey") == null && tag.hasByteArray("PrivateKey") && tag.hasByteArray("PublicKey")) {
PrivateKey key = EncryptUtil.decodePrivateKey(tag.getByteArray("PrivateKey"));
PublicKey pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("PublicKey"));
if(key != null && pubkey != null)
this.keyPair = new KeyPair(pubkey, key);
}
return time;
}
catch(Exception e) {
Log.IO.error(e, "Fehler beim Lesen von " + file);
for(SVar svar : this.variables.values()) {
svar.set(svar.def, svar.noDef, false);
}
}
}
Log.IO.info("Erstelle neue Welt und Konfiguration");
return World.START_TIME;
}
private void loadDimension(Dimension dim) {
File file = new File(new File(new File("chunk"), UniverseRegistry.getName(dim)), "data.cdt");
TagObject tag = null;
if(file.exists()) {
try {
tag = TagObject.readGZip(file);
}
catch(Exception e) {
Log.IO.error(e, "Konnte Weltdaten %s nicht laden", file);
}
}
dim.readData(tag == null ? new TagObject() : tag);
if(tag == null || !tag.hasLong("Seed")) {
Random rand = new Random();
if(!Vars.seed.isEmpty())
rand.setSeed((long)Vars.seed.hashCode() ^ ~((long)UniverseRegistry.getName(dim).hashCode()));
dim.setSeed(rand.longv());
Log.TICK.info("Startwert für %s: %d" + (Vars.seed.isEmpty() ? "" : " von Basiswert '%s'"), dim.getDisplay(), dim.getSeed(), Vars.seed);
}
}
private void saveDimension(Dimension dim) {
TagObject tag = dim.writeData(false);
File file = new File(new File(new File("chunk"), UniverseRegistry.getName(dim)), "data.cdt");
try {
file.getParentFile().mkdirs();
TagObject.writeGZip(tag, file);
}
catch(Exception e) {
Log.IO.error(e, "Konnte Weltdaten %s nicht speichern", file);
}
}
private void setCallback(Runnable callback, String ... vars) {
for(String key : vars) {
this.variables.get(key).setCallback(callback);
}
}
public Map<String, SVar> getVariables() {
return this.variables;
}
public boolean setVariableOverride(String id, Runnable func) {
Map<String, Object> map = Maps.newHashMap();
for(Entry<String, SVar> entry : this.variables.entrySet()) {
map.put(entry.getKey(), entry.getValue().getRaw());
}
func.run();
for(Iterator<Entry<String, Object>> iter = map.entrySet().iterator(); iter.hasNext();) {
Entry<String, Object> entry = iter.next();
SVar sv = this.variables.get(entry.getKey());
if(!sv.getRaw().equals(entry.getValue())) {
for(Iterator<Entry<String, Map<String, Object>>> oiter = this.overrides.entrySet().iterator(); oiter.hasNext();) {
Entry<String, Map<String, Object>> oentry = oiter.next();
Object value = oentry.getValue().remove(entry.getKey());
if(value == null)
continue;
if(oentry.getValue().isEmpty())
oiter.remove();
if(sv.getRaw().equals(value))
iter.remove();
break;
}
sv.set(sv.get(), false, true);
if(sv.sync)
this.sendPacket(new SPacketServerConfig(entry.getKey(), sv.getRaw()));
}
else {
iter.remove();
}
}
if(!map.isEmpty())
this.overrides.put(id, map);
return !map.isEmpty();
}
public boolean unsetVariableOverride(String id) {
Map<String, Object> map = this.overrides.remove(id);
if(map == null)
return false;
for(Entry<String, Object> entry : map.entrySet()) {
SVar sv = this.variables.get(entry.getKey());
if(!sv.getRaw().equals(entry.getValue())) {
sv.set("" + entry.getValue(), false, true);
if(sv.sync)
this.sendPacket(new SPacketServerConfig(entry.getKey(), sv.getRaw()));
}
}
return true;
}
public String removeVariableOverride(String var) {
for(Iterator<Entry<String, Map<String, Object>>> iter = this.overrides.entrySet().iterator(); iter.hasNext();) {
Entry<String, Map<String, Object>> entry = iter.next();
Object value = entry.getValue().remove(var);
if(value == null)
continue;
String id = entry.getKey();
if(entry.getValue().isEmpty())
iter.remove();
SVar sv = this.variables.get(var);
if(!sv.getRaw().equals(value)) {
sv.set("" + value, false, true);
if(sv.sync)
this.sendPacket(new SPacketServerConfig(var, sv.getRaw()));
}
return id;
}
return null;
}
public String getVariableOverride(String var) {
for(Entry<String, Map<String, Object>> entry : this.overrides.entrySet()) {
if(entry.getValue().containsKey(var))
return entry.getKey();
}
return null;
}
public String getVariableOverrideValue(String var) {
for(Entry<String, Map<String, Object>> entry : this.overrides.entrySet()) {
if(entry.getValue().containsKey(var))
return "" + entry.getValue().get(var);
}
return this.variables.get(var).get();
}
private Server() {
for(Class<?> clazz : new Class[] {Vars.class, SVars.class}) {
for(Field field : clazz.getDeclaredFields()) {
if(field.isAnnotationPresent(Var.class)) {
if(!Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers()))
throw new IllegalArgumentException("Feld für Variable " + field + " muss statisch und änderbar sein!");
Var value = field.getAnnotation(Var.class);
if(value.name().isEmpty())
throw new IllegalArgumentException("Variablenname von " + field + " kann nicht leer sein!");
if(this.variables.containsKey(value.name()))
throw new IllegalArgumentException("Variable " + value.name() + " existiert bereits!");
this.variables.put(value.name(), new SVar(field, value, clazz == Vars.class));
}
}
}
this.setCallback(new Runnable() {
public void run() {
for(WorldServer world : Server.this.getWorlds()) {
world.updateViewRadius();
}
}
}, "viewDistance");
this.setCallback(new Runnable() {
public void run() {
Server.this.bind(SVars.port);
}
}, "port");
this.setCallback(new Runnable() {
public void run() {
if((!SVars.password.isEmpty() && SVars.password.length() < 8) || SVars.password.length() > IPlayer.MAX_PASS_LENGTH)
Log.IO.warn("Passwort muss aus 8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen bestehen, Login wird nicht möglich sein");
}
}, "password");
}
public CommandEnvironment getScriptEnvironment() {
return this.scriptEnv;
}
public List<String> getPlayerFilenames() {
String[] list = new File("players").list();
if(list == null)
return Lists.newArrayList();
List<String> names = Lists.newArrayList();
for(int i = 0; i < list.length; ++i) {
if(list[i].endsWith(".cdt")) {
String name = list[i].substring(0, list[i].length() - 4);
if(IPlayer.isValidUser(name))
names.add(name);
}
}
return names;
}
public void saveWorldInfo() {
this.saveServerConfig(this.space.getDayTime());
for(Dimension dim : UniverseRegistry.getDimensions()) {
this.saveDimension(dim);
}
WorldServer.saveWarps(this.warps);
}
public TagObject loadPlayerData(String user) {
if(!IPlayer.isValidUser(user))
return null;
TagObject tag = null;
try {
File dat = new File(new File("players"), user + ".cdt");
if(dat.exists() && dat.isFile()) {
tag = TagObject.readGZip(dat);
}
}
catch(Exception e) {
Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht laden");
}
return tag;
}
public void writePlayerData(String user, TagObject tag) {
if(!IPlayer.isValidUser(user))
return;
try {
File tmp = new File(new File("players"), user + ".cdt.tmp");
File dat = new File(new File("players"), user + ".cdt");
TagObject.writeGZip(tag, tmp);
if(dat.exists()) {
dat.delete();
}
tmp.renameTo(dat);
}
catch(Exception e) {
Log.IO.error(e, "Konnte Spielerdaten für " + user + " (offline) nicht speichern");
}
}
public Position getOfflinePosition(String user) {
TagObject tag = this.loadPlayerData(user);
if(tag == null)
return null;
double posX = tag.getDouble("PosX");
double posY = tag.getDouble("PosY");
double posZ = tag.getDouble("PosZ");
float rotYaw = tag.getFloat("Yaw");
float rotPitch = tag.getFloat("Pitch");
Dimension dimension = UniverseRegistry.getDimension(tag.getString("Dimension"));
return dimension == null ? null : new Position(posX, posY, posZ, rotYaw, rotPitch, dimension);
}
public void saveAllWorlds(boolean message) {
if(message)
Log.TICK.info("Speichere Welt");
this.saveWorldInfo();
for(WorldServer world : this.worlds) {
world.saveAllChunks();
}
}
public void unloadWorld(WorldServer world) {
if(world != this.space)
this.unloadWorld(world.dimension);
}
private void unloadWorld(Dimension dim) {
WorldServer world = this.dimensions.get(UniverseRegistry.getId(dim));
if(world != null && world.players.isEmpty()) {
world.saveAllChunks();
Region.finishWrite();
this.worlds.remove(world);
this.dimensions.remove(UniverseRegistry.getId(dim));
}
}
public void run(long time) {
Region.loadMap();
long wtime = this.loadServerConfig();
for(Dimension dim : UniverseRegistry.getDimensions()) {
this.loadDimension(dim);
}
Dimensions.updateDimensions();
if(this.keyPair == null) {
Log.SYSTEM.info("Generiere neues Schlüsselpaar");
this.keyPair = EncryptUtil.createKeypair();
}
User.loadDatabase(this.users);
this.worlds.add(this.space = new WorldServer(this, wtime, Space.INSTANCE, UniverseRegistry.getGenerator(Space.INSTANCE)));
this.dimensions.put(UniverseRegistry.getId(this.space.dimension), this.space);
new File("players").mkdirs();
this.setTpsTarget(20.0f);
for(Dimension dim : UniverseRegistry.getDimensions()) {
if(WorldServer.needsLoading(dim)) {
this.getWorld(dim).loadForcedChunks();
}
WorldServer.loadWarps(dim, this.warps);
}
if(SVars.port >= 0)
this.bind(SVars.port);
else
Log.SYSTEM.warn("Kein Port definiert, verwende 'sv port <1024-32767>' um einen Hosting-Port festzulegen");
if(SVars.accessRequired && SVars.password.length() < 8)
Log.SYSTEM.warn("Kein Passwort definiert, verwende 'sv password <8-" + IPlayer.MAX_PASS_LENGTH + " Zeichen>' um ein Zugangspasswort festzulegen");
Thread con = new Thread(new Runnable() {
private final BufferedReader reader = new BufferedReader(new InputStreamReader(new BufferedInputStream(System.in)));
public void run() {
while(true) {
String line;
try {
line = this.reader.readLine();
}
catch(IOException e) {
line = null;
}
if(line == null)
break;
final String cmd = line;
Server.this.schedule(new Runnable() {
public void run() {
Server.this.scriptEnv.execute(cmd, Server.this);
}
});
}
}
}, "Server console handler");
con.setDaemon(true);
con.start();
Log.SYSTEM.info("Server gestartet in " + String.format("%.1f", (double)(System.currentTimeMillis() - time) / 1000.0) + " Sekunden");
while(this.running) {
this.currentTime = System.nanoTime() / 1000L;
this.timePassed = this.currentTime - this.lastSchedule;
this.lastSchedule = this.currentTime;
if(this.timePassed < 0L) {
Log.TICK.warn("Zeit lief rückwärts! Hat sich die Systemzeit geändert?");
this.timePassed = 0L;
this.ticksTodo = 0L;
}
if(this.timePassed > 2000000L && this.currentTime - this.lastWarning >= 15000000L) {
Log.TICK.warn("Kann Server-Tick nicht aufrecht erhalten! Hat sich die Systemzeit geändert, oder ist der Server überlastet? " +
"Liege " + (this.timePassed / 1000L) + " ms zurück, überspringe " + ((this.timePassed * this.tpsTarget) / 1000000000L) + " Tick(s)");
this.timePassed = 0L;
this.lastWarning = this.currentTime;
this.ticksTodo = 0L;
}
this.ticksTodo += (this.timePassed * this.tpsTarget) / 1000L;
while(this.ticksTodo >= 1000000L && (System.nanoTime() / 1000L - this.currentTime) < 10000L) {
this.ticksTodo -= 1000000L;
this.tick();
++this.ticksDone;
}
if((this.currentTime - this.lastPoll) >= 1000000L) {
this.tpsRate = (this.ticksDone * 1000000000L) / (this.currentTime - this.lastPoll);
this.lastPoll = this.currentTime;
this.ticksDone = 0L;
}
this.started = true;
}
try {
this.stopServer();
this.stopped = true;
}
catch(Throwable e) {
Log.SYSTEM.error(e, "Fehler beim Beenden des Servers");
}
finally {
this.stopped = true;
Log.SYSTEM.info("Server wurde beendet");
}
}
public void preload(WorldServer world, int bx, int bz) {
int done = 0;
int total = Vars.distance * 2 + 1;
total *= total;
Log.TICK.info("Generiere und lade Welt");
bx = bx >> 4;
bz = bz >> 4;
long last = System.currentTimeMillis();
for(int x = -Vars.distance; x <= Vars.distance; x++) {
for(int z = -Vars.distance; z <= Vars.distance; z++) {
long time = System.currentTimeMillis();
if(time - last > 1000L) {
Log.TICK.info("Bereite Spawnbereich vor" + ": " + (done * 100 / total) + "%");
last = time;
}
++done;
world.loadChunk(bx + x, bz + z);
}
}
}
private void tick() {
long now = System.currentTimeMillis();
synchronized(this.queue) {
while(!this.queue.isEmpty()) {
Runnable task = this.queue.poll();
try {
task.run();
}
catch(Throwable e) {
if(!(e instanceof ThreadQuickExitException))
Log.SYSTEM.error(e, "Fehler beim Ausführen von Server-Task " + task);
}
}
}
if(++this.syncTimer == 20) {
for(Player conn : this.players) {
this.sendPacket(new SPacketTimeUpdate(this.space.getDayTime(), Dimensions.formatTime(conn.getPresentEntity() == null ? this.space : (WorldServer)conn.getPresentEntity().worldObj, conn.getPresentEntity(), true), this.getInfo()));
}
this.syncTimer = 0;
}
this.ticked.clear();
this.ticked.addAll(this.worlds);
for(WorldServer world : this.ticked) {
world.tick();
world.updateEntities();
world.updateTrackedEntities();
if(world != this.space && world.shouldUnload())
this.unload.add(world.dimension);
}
for(Dimension dim : this.unload) {
this.unloadWorld(dim);
}
this.networkTick();
if(++this.pingTimer > 600) {
this.sendPacket(new SPacketPlayerListItem((List)this.getPlayers()));
this.pingTimer = 0;
}
if(SVars.saveInterval > 0 && ++this.saveTimer >= SVars.saveInterval) {
this.saveAllPlayerData(false);
this.saveAllWorlds(false);
this.saveTimer = 0;
}
Log.flushLog();
this.tickTimes[this.perfTimer++] = System.currentTimeMillis() - now;
if(this.perfTimer == 100) {
this.perfTimer = 0;
}
}
public WorldServer getSpace() {
return this.space;
}
public WorldServer getWorld(Dimension dim) {
if(dim == null)
return null;
WorldServer world = this.dimensions.get(UniverseRegistry.getId(dim));
if(world == null) {
world = new WorldServer(this, this.space.getDayTime(), dim, UniverseRegistry.getGenerator(dim));
this.worlds.add(world);
this.dimensions.put(UniverseRegistry.getId(dim), world);
}
return world;
}
public WorldServer getWorldNoLoad(Dimension dim) {
return dim == null ? null : this.dimensions.get(UniverseRegistry.getId(dim));
}
public WorldServer getWorld(String alias) {
Dimension dim = UniverseRegistry.getDimension(alias);
return dim == null ? null : this.getWorld(dim);
}
public List<WorldServer> getWorlds() {
return this.worlds;
}
public void resetSaveTimer() {
this.saveTimer = 0;
}
public long[] getTickTimes() {
return this.tickTimes;
}
public float getAverageTps() {
long total = 0L;
for(int z = 0; z < this.tickTimes.length; z++) {
total += this.tickTimes[z];
}
return (float)total / (float)this.tickTimes.length;
}
public long getLastTick() {
return this.tickTimes[this.perfTimer == 0 ? 99 : (this.perfTimer - 1)];
}
public float getTpsRate() {
return ((float)this.tpsRate) / 1000.0f;
}
public float getTpsTarget() {
return ((float)this.tpsTarget) / 1000.0f;
}
public void setTpsTarget(float target) {
this.tpsTarget = this.tpsRate = (long)(target * 1000.0f);
this.currentTime = this.lastSchedule = this.lastPoll = System.nanoTime() / 1000L;
this.timePassed = this.ticksTodo = this.ticksDone = 0L;
}
public List<String> getAllPlayerNames() {
List<String> list = new ArrayList<String>(this.players.size());
for(int z = 0; z < this.players.size(); z++) {
list.add(this.players.get(z).getUser());
}
return list;
}
public List<String> getAllUserNames() {
return Lists.newArrayList(this.users.keySet());
}
public List<Player> getPlayers() {
return this.players;
}
public Collection<User> getUsers() {
return this.users.values();
}
public Player getPlayer(String user) {
return this.online.get(user);
}
public User getUser(String user) {
return this.users.get(user);
}
public void schedule(Runnable task) {
if(!this.isMainThread() && !this.stopped) {
synchronized(this.queue) {
this.queue.add(task);
}
}
else {
try {
task.run();
}
catch(Throwable e) {
Log.SYSTEM.error(e, "Fehler beim sofortigen Ausführen von Server-Task " + task);
}
}
}
public boolean isMainThread() {
return Thread.currentThread() == this.thread;
}
public Position getRandomSpawnPosition(WorldPos origin) {
WorldServer world = this.getWorld(origin.getDimension());
world = world == null ? this.space : world;
BlockPos pos = origin;
int radius = SVars.spawnRadius;
if(radius > 0) {
pos = world.getHighestFreePos(pos.add(
ExtMath.clampi(world.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1), 0,
ExtMath.clampi(world.rand.excl(-radius, radius), -World.MAX_SIZE + 1, World.MAX_SIZE - 1)));
}
int y = pos.getY();
while(world.getState(new BlockPos(pos.getX(), y, pos.getZ())).getBlock().getMaterial().blocksMovement() && y < 511)
y++;
return new Position(pos.getX() + 0.5, (double)y, pos.getZ() + 0.5, -180.0f + world.rand.floatv() * 360.0f, 0.0f, world);
}
public void addUser(User user) {
this.users.put(user.getUser(), user);
}
public void addPlayer(NetConnection connection, String loginUser) {
TagObject tag = this.readPlayer(loginUser);
Player conn = new Player(this, connection, loginUser);
if(tag != null)
conn.readTags(tag);
if(SVars.compression >= 0) {
connection.sendPacket(new RPacketEnableCompression(SVars.compression), new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
connection.setCompressionTreshold(SVars.compression);
}
});
}
connection.sendPacket(new RPacketLoginSuccess());
connection.setNetHandler(conn);
this.players.add(conn);
User user = this.users.remove(loginUser);
if(user != null)
conn.copyFrom(user);
this.users.put(loginUser, conn);
this.online.put(loginUser, conn);
tag = conn.readCharacter();
WorldServer world = tag == null ? this.space : this.getWorld(tag.getString("Dimension"));
world = world == null ? this.space : world;
EntityNPC player = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(EntityHuman.class) : tag.getString("id"));
if(tag != null)
player.readTags(tag);
else
player.onInitialSpawn(null);
if(tag == null)
player.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f);
Log.NETWORK.info(loginUser + "[" + connection.getCutAddress() + "] hat sich mit Objekt-ID "
+ player.getId() + " auf " + UniverseRegistry.getName(world.dimension) + ": "
+ String.format("%.2f %.2f %.2f", player.posX, player.posY, player.posZ) + " verbunden (" + (tag == null ? "Charakter-Editor" : "'" + player.getRawName() + "'") + ")");
List<Pair<String, Object>> vars = Lists.newArrayList();
for(Entry<String, SVar> var : this.variables.entrySet()) {
if(var.getValue().sync)
vars.add(new Pair(var.getKey(), var.getValue().getRaw()));
}
conn.sendPacket(new SPacketServerConfig(vars));
conn.sendPacket(new SPacketDimensions(Dimensions.getDimensionData()));
conn.sendPacket(new SPacketJoinGame(player.getId(), world.dimension, UniverseRegistry.getName(world.dimension), EntityRegistry.getEntityID(player), tag == null));
conn.sendPacket(new SPacketHeldItemChange(player.getSelectedIndex()));
this.sendPacket(new SPacketPlayerListItem(false, conn));
world.spawnEntityInWorld(player);
this.preparePlayer(player, null);
for(int i = 0; i < this.players.size(); ++i) {
Player other = this.players.get(i);
conn.sendPacket(new SPacketPlayerListItem(false, other));
}
conn.sendPacket(new SPacketSkin(player.getId(), player.getSkin())); // , player.getModel()));
conn.setPlayerLocation(player.posX, player.posY, player.posZ, player.rotYaw, player.rotPitch);
this.updateTimeAndWeatherForPlayer(conn, world);
for(StatusEffect effect : player.getEffects()) {
conn.sendPacket(new SPacketEntityEffect(player.getId(), effect));
}
conn.sendPacket(new SPacketPlayerAbilities(player));
conn.addSelfToInternalCraftingInventory();
conn.onConnect();
}
public void removePlayer(Player conn) {
EntityNPC player = conn.getEntity();
player.unmount();
this.writePlayer(conn);
WorldServer world = (WorldServer)player.getServerWorld();
world.removeEntity(player);
world.removePlayer(player);
this.players.remove(conn);
this.online.remove(conn.getUser());
User user = new User(conn.getUser());
user.copyFrom(conn);
this.users.put(conn.getUser(), user);
this.sendPacket(new SPacketPlayerListItem(true, conn));
}
private void preparePlayer(EntityNPC player, WorldServer oldWorld) {
WorldServer newWorld = (WorldServer)player.getServerWorld();
if(oldWorld != null) {
oldWorld.removePlayer(player);
}
newWorld.addPlayer(player);
newWorld.loadChunk((int)player.posX >> 4, (int)player.posZ >> 4);
}
private TagObject readPlayer(String user) {
TagObject tag = null;
try {
File dat = new File(new File("players"), user + ".cdt");
if(dat.exists() && dat.isFile()) {
tag = TagObject.readGZip(dat);
}
}
catch(Exception e) {
Log.IO.error(e, "Konnte Spielerdaten für " + user + " nicht laden");
}
return tag;
}
private void writePlayer(Player conn) {
try {
TagObject tag = new TagObject();
EntityNPC entity = conn.getPresentEntity();
if(entity != null) {
TagObject etag = new TagObject();
entity.writeTags(etag);
etag.setString("Dimension", UniverseRegistry.getName(entity.worldObj.dimension));
etag.setString("id", EntityRegistry.getEntityString(entity));
conn.writeCharacter(etag);
}
conn.writeTags(tag);
File tmp = new File(new File("players"), conn.getUser() + ".cdt.tmp");
File dat = new File(new File("players"), conn.getUser() + ".cdt");
TagObject.writeGZip(tag, tmp);
if(dat.exists()) {
dat.delete();
}
tmp.renameTo(dat);
}
catch(Exception e) {
Log.IO.error(e, "Konnte Spielerdaten für " + conn.getUser() + " nicht speichern");
}
}
public void recreatePlayer(Player conn) {
EntityNPC old = conn.getEntity();
BlockPos pos = old.getPosition();
WorldServer oldWorld = (WorldServer)old.getServerWorld();
oldWorld.removePlayerFromTrackers(old);
oldWorld.untrackEntity(old);
oldWorld.removePlayer(old);
oldWorld.removePlayerEntityDangerously(old);
WorldPos bed = old.getSpawnPoint();
WorldPos origin = old.getOrigin();
BlockPos spawn = null;
String message = null;
WorldServer world = bed == null ? this.space : this.getWorld(bed.getDimension());
if(world == null) {
world = this.space;
bed = null;
message = "Die Dimension deines Einstiegspunkts wurde gelöscht oder zerstört";
}
else if(bed != null) {
spawn = EntityNPC.getBedSpawnLocation(world, bed);
if(spawn == null) {
world = this.space;
bed = null;
message = "Dein Einstiegspunkt wurde blockiert oder dein Bett zerstört";
}
}
if(bed == null) {
world = this.getWorld(origin.getDimension());
world = world == null ? this.space : world;
}
EntityNPC nplayer = conn.createPlayer(world, EntityRegistry.getEntityString(old));
conn.copyPlayer(old);
nplayer.setId(old.getId());
nplayer.setOrigin(origin);
if(bed != null) {
nplayer.setLocationAndAngles((double)((float)spawn.getX() + 0.5F), (double)((float)spawn.getY() + 0.1F),
(double)((float)spawn.getZ() + 0.5F), 0.0F, 0.0F);
nplayer.setSpawnPoint(bed);
}
else {
Position rpos = this.getRandomSpawnPosition(origin);
nplayer.setLocationAndAngles(rpos.x(), rpos.y(), rpos.z(), rpos.yaw(), rpos.pitch());
}
world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4);
if(bed != null ? SVars.checkBed : SVars.spawnRadius >= 0) {
while(!world.getCollidingBoundingBoxes(nplayer, nplayer.getEntityBoundingBox()).isEmpty() && nplayer.posY < 512.0D) {
nplayer.setPosition(nplayer.posX, nplayer.posY + 1.0D, nplayer.posZ);
}
}
conn.sendPacket(new SPacketRespawn(world.dimension != oldWorld.dimension ? world.dimension : null, world.dimension != oldWorld.dimension ? UniverseRegistry.getName(world.dimension) : null, EntityRegistry.getEntityID(nplayer), conn.isInEditor()));
conn.setPlayerLocation(nplayer.posX, nplayer.posY, nplayer.posZ, nplayer.rotYaw, nplayer.rotPitch);
conn.sendPacket(new SPacketSetExperience(nplayer.experience, nplayer.experienceTotal, nplayer.experienceLevel));
this.updateTimeAndWeatherForPlayer(conn, world);
world.addPlayer(nplayer);
world.spawnEntityInWorld(nplayer);
conn.addSelfToInternalCraftingInventory();
nplayer.setHealth(nplayer.getHealth());
conn.sendPlayerAbilities();
if(message != null)
conn.addHotbar(Color.DARK_RED + message);
conn.addFeed(Color.RED + "* Bei %d, %d, %d in Dimension %s gestorben", pos.getX(), pos.getY(), pos.getZ(),
old.worldObj.dimension.getDisplay());
}
public TagObject swapPlayer(Player conn, TagObject tag, Class<? extends EntityNPC> clazz) {
EntityNPC old = conn.getEntity();
old.unmount();
TagObject oldTag = new TagObject();
old.writeTags(oldTag);
oldTag.setString("Dimension", UniverseRegistry.getName(old.worldObj.dimension));
oldTag.setString("id", EntityRegistry.getEntityString(old));
WorldServer oldWorld = (WorldServer)old.getServerWorld();
oldWorld.removePlayerFromTrackers(old);
oldWorld.untrackEntity(old);
oldWorld.removePlayer(old);
oldWorld.removePlayerEntityDangerously(old);
WorldServer world = tag == null ? this.space : this.getWorld(tag.getString("Dimension"));
world = world == null ? this.space : world;
EntityNPC nplayer = conn.createPlayer(world, tag == null ? EntityRegistry.getEntityString(clazz) : tag.getString("id"));
if(tag != null)
nplayer.readTags(tag);
else
nplayer.onInitialSpawn(null);
nplayer.setId(old.getId());
if(tag == null)
nplayer.moveToBlockPosAndAngles(new BlockPos(0, 16384, 0), 0.0f, 0.0f);
world.loadChunk((int)nplayer.posX >> 4, (int)nplayer.posZ >> 4);
world.addPlayer(nplayer);
world.spawnEntityInWorld(nplayer);
conn.sendPacket(new SPacketRespawn(world.dimension != oldWorld.dimension ? world.dimension : null, world.dimension != oldWorld.dimension ? UniverseRegistry.getName(world.dimension) : null, EntityRegistry.getEntityID(nplayer), conn.isInEditor()));
conn.sendPacket(new SPacketSkin(nplayer.getId(), nplayer.getSkin())); // , nplayer.getModel()));
conn.setPlayerLocation(nplayer.posX, nplayer.posY, nplayer.posZ, nplayer.rotYaw, nplayer.rotPitch);
conn.sendPacket(new SPacketSetExperience(nplayer.experience, nplayer.experienceTotal, nplayer.experienceLevel));
conn.addSelfToInternalCraftingInventory();
nplayer.setHealth(nplayer.getHealth());
this.updateTimeAndWeatherForPlayer(conn, world);
this.syncPlayerInventory(nplayer);
for(StatusEffect effect : nplayer.getEffects()) {
conn.sendPacket(new SPacketEntityEffect(nplayer.getId(), effect));
}
conn.sendPlayerAbilities();
return oldTag;
}
public void transferToDimension(EntityNPC player, Dimension dimension, BlockPos pos, float yaw, float pitch, PortalType portal) {
WorldServer oldWorld = (WorldServer)player.getServerWorld(); // this.getWorld(player.dimension);
WorldServer newWorld = this.getWorld(dimension);
player.connection.sendPacket(new SPacketRespawn(newWorld.dimension, UniverseRegistry.getName(newWorld.dimension), EntityRegistry.getEntityID(player), player.connection.isInEditor()));
oldWorld.removePlayerEntityDangerously(player);
player.dead = false;
this.placeInDimension(player, oldWorld, newWorld, pos, portal);
if(player.isEntityAlive()) {
newWorld.spawnEntityInWorld(player);
newWorld.updateEntity(player, false);
}
// player.loadedChunks.clear(); // FIX ??
this.preparePlayer(player, oldWorld);
player.connection.setPlayerLocation(player.posX, player.posY, player.posZ, pos != null ? yaw : player.rotYaw, pos != null ? pitch : player.rotPitch);
if(pos != null)
player.setRotationYawHead(yaw);
this.updateTimeAndWeatherForPlayer((Player)player.connection, newWorld);
this.syncPlayerInventory(player);
for(StatusEffect effect : player.getEffects()) {
player.connection.sendPacket(new SPacketEntityEffect(player.getId(), effect));
}
player.connection.sendPlayerAbilities();
}
public void placeInDimension(Entity entity, WorldServer oldWorld, WorldServer world, BlockPos pos, PortalType portal) {
double newX = entity.posX;
double newY = entity.posY;
double newZ = entity.posZ;
float newYaw = entity.rotYaw;
float newPitch = entity.rotPitch;
if(pos != null) {
newX = ((double)pos.getX()) + 0.5;
newY = pos.getY();
newZ = ((double)pos.getZ()) + 0.5;
}
else {
pos = world.getHighestFreePos(new BlockPos(newX, 0, newZ));
newX = ((double)pos.getX()) + 0.5;
newY = (double)pos.getY();
newZ = ((double)pos.getZ()) + 0.5;
}
newX = (double)ExtMath.clampd(newX, -World.MAX_SIZE + 1, World.MAX_SIZE - 1);
newZ = (double)ExtMath.clampd(newZ, -World.MAX_SIZE + 1, World.MAX_SIZE - 1);
entity.setLocationAndAngles(newX, newY, newZ, newYaw, newPitch);
if(entity.isEntityAlive()) {
oldWorld.updateEntity(entity, false);
}
if(entity.isEntityAlive()) {
entity.setLocationAndAngles(newX, entity.posY, newZ, entity.rotYaw, entity.rotPitch);
if(portal != null)
world.makePortal(new BlockPos(ExtMath.floord(entity.posX)
,ExtMath.floord(entity.posY)
,ExtMath.floord(entity.posZ)), 128, portal);
}
entity.setWorld(world);
}
public void sendPacket(Packet packet) {
for(Player conn : this.players) {
conn.sendPacket(packet);
}
}
public void saveAllPlayerData(boolean message) {
if(message) {
Log.TICK.info("Speichere Spielerdaten");
}
for(Player conn : this.players) {
this.writePlayer(conn);
}
User.saveDatabase(this.users);
}
private void updateTimeAndWeatherForPlayer(Player conn, WorldServer world) {
conn.sendPacket(new SPacketTimeUpdate(world.getDayTime(), Dimensions.formatTime(world, conn.getPresentEntity(), true), this.getInfo()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.SET_WEATHER, world.getWeather().ordinal()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.RAIN_STRENGTH, world.getRainStrength()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.DARKNESS, world.getDarkness()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.FOG_STRENGTH, world.getFogStrength()));
conn.sendPacket(new SPacketChangeGameState(SPacketChangeGameState.Action.TEMPERATURE, world.getTempOffset()));
}
public void syncPlayerInventory(EntityNPC player) {
player.connection.sendContainer(player.inventoryContainer, player.inventoryContainer.getInventory());
player.connection.setPlayerHealthUpdated();
player.connection.sendPacket(new SPacketHeldItemChange(player.getSelectedIndex()));
}
private void terminateEndpoint(String message) {
if(this.started)
for(Player conn : Lists.newArrayList(this.players)) {
conn.disconnect(message);
}
synchronized(this.thread) {
if(this.endpoint != null) {
Log.NETWORK.info("Schließe Port");
try {
this.endpoint.channel().close().sync();
this.endpoint = null;
}
catch(InterruptedException e) {
Log.NETWORK.warn("Unterbrochen beim Schließen des Kanals");
}
}
}
}
private void networkTick() {
synchronized(this.clients) {
Iterator<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) {
Log.NETWORK.error(e, "Konnte Paket von " + manager.getCutAddress() + " nicht verarbeiten");
manager.sendPacket(new SPacketDisconnect(e.getMessage()), 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;
}
private void stopServer() {
if(!this.stopped) {
Log.SYSTEM.info("Beende Server");
this.terminateEndpoint(this.endMessage);
if(this.started) {
this.saveAllPlayerData(true);
this.saveAllWorlds(true);
Region.finishWrite();
}
}
}
public void bind(int port) {
synchronized(this.thread) {
if(port >= 0) {
try {
if(this.endpoint != null)
this.terminateEndpoint("Wechsele auf Port " + port);
Log.NETWORK.info("Öffne Port %d auf 0.0.0.0 (Timeout %ds)", port, SVars.timeout);
this.endpoint = ((ServerBootstrap)((ServerBootstrap)(new ServerBootstrap()).channel(NioServerSocketChannel.class)).childHandler(new ChannelInitializer<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(SVars.timeout)))
.addLast((String)"splitter", (ChannelHandler)(new PacketSplitter()))
.addLast((String)"decoder", (ChannelHandler)(new PacketDecoder(true)))
.addLast((String)"prepender", (ChannelHandler)(new PacketPrepender()))
.addLast((String)"encoder", (ChannelHandler)(new PacketEncoder(false)));
NetConnection manager = new NetConnection();
Server.this.clients.add(manager);
channel.pipeline().addLast((String)"packet_handler", (ChannelHandler)manager);
manager.setNetHandler(new HandshakeHandler(Server.this, manager));
}
}).group(this.eventGroup).localAddress((InetAddress)null, port)).bind().syncUninterruptibly();
}
catch(Throwable e) {
Log.NETWORK.error(e, "**** KONNTE NICHT AN PORT " + port + " ANBINDEN!");
}
}
else {
if(this.endpoint != null)
this.terminateEndpoint("Trenne Verbindung");
}
}
}
public void shutdown(String message) {
this.running = false;
this.endMessage = message;
}
public String getInfo() {
return String.format("Server: %.3f T/s (%.1fx), Z: %.1f T/s, D: %.3f ms", this.getTpsRate(), this.getTpsRate() / 20.0f, this.getTpsTarget(), this.getAverageTps()) + "\n" +
"Geladen: " + this.getWorlds().size() + " Welten, " + WorldServer.getLoadedInfo(this);
}
public PublicKey getPublicKey() {
return this.keyPair.getPublic();
}
public PrivateKey getPrivateKey() {
return this.keyPair.getPrivate();
}
public void log(String msg) {
Log.CONSOLE.info(msg);
}
public Position getExecPos() {
return this.execPos;
}
public void setExecPos(Position pos) {
this.execPos = pos;
}
public boolean isConsole() {
return true;
}
}