1433 lines
49 KiB
Java
Executable file
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;
|
|
}
|
|
}
|