Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
1package net.minecraft.server;
2
3import com.legacyminecraft.poseidon.Poseidon;
4import com.legacyminecraft.poseidon.PoseidonConfig;
5import com.legacyminecraft.poseidon.util.CrackedAllowlist;
6import com.legacyminecraft.poseidon.util.ServerLogRotator;
7import com.legacyminecraft.poseidon.watchdog.WatchDogThread;
8import jline.ConsoleReader;
9import joptsimple.OptionSet;
10import org.bukkit.World.Environment;
11import org.bukkit.craftbukkit.CraftServer;
12import org.bukkit.craftbukkit.LoggerOutputStream;
13import org.bukkit.craftbukkit.command.ColouredConsoleSender;
14import org.bukkit.craftbukkit.scheduler.CraftScheduler;
15import org.bukkit.craftbukkit.util.ServerShutdownThread;
16import org.bukkit.event.server.ServerCommandEvent;
17import org.bukkit.event.world.WorldInitEvent;
18import org.bukkit.event.world.WorldLoadEvent;
19import org.bukkit.event.world.WorldSaveEvent;
20import org.bukkit.generator.ChunkGenerator;
21import org.bukkit.plugin.PluginLoadOrder;
22import uk.betacraft.uberbukkit.UberbukkitConfig;
23
24import java.io.File;
25import java.io.IOException;
26import java.io.PrintStream;
27import java.net.InetAddress;
28import java.net.UnknownHostException;
29import java.util.*;
30import java.util.logging.Level;
31import java.util.logging.Logger;
32
33// CraftBukkit start
34//import com.projectposeidon.johnymuffin.UUIDCacheFile;
35// CraftBukkit end
36
37public class MinecraftServer implements Runnable, ICommandListener {
38
39 public static Logger log = Logger.getLogger("Minecraft");
40 public static HashMap trackerList = new HashMap();
41 public NetworkListenThread networkListenThread;
42 public PropertyManager propertyManager;
43 // public WorldServer[] worldServer; // CraftBukkit - removed!
44 public ServerConfigurationManager serverConfigurationManager;
45 public ConsoleCommandHandler consoleCommandHandler; // CraftBukkit - made public
46 private boolean isRunning = true;
47 public boolean isStopped = false;
48 int ticks = 0;
49 public String i;
50 public int j;
51 private List r = new ArrayList();
52 private List s = Collections.synchronizedList(new ArrayList());
53 // public EntityTracker[] tracker = new EntityTracker[2]; // CraftBukkit - removed!
54 public boolean onlineMode;
55 public boolean spawnAnimals;
56 public boolean pvpMode;
57 public boolean allowFlight;
58
59 // CraftBukkit start
60 public List<WorldServer> worlds = new ArrayList<WorldServer>();
61 public CraftServer server;
62 public OptionSet options;
63 public ColouredConsoleSender console;
64 public ConsoleReader reader;
65 public static int currentTick;
66 // CraftBukkit end
67
68 //Poseidon Start
69// private WatchDogThread watchDogThread;
70 private boolean modLoaderSupport = false;
71// private PoseidonVersionChecker poseidonVersionChecker;
72 //Poseidon End
73
74 public MinecraftServer(OptionSet options) { // CraftBukkit - adds argument OptionSet
75 new ThreadSleepForever(this);
76
77 // CraftBukkit start
78 this.options = options;
79 try {
80 this.reader = new ConsoleReader();
81 } catch (IOException ex) {
82 Logger.getLogger(MinecraftServer.class.getName()).log(Level.SEVERE, null, ex);
83 }
84 Runtime.getRuntime().addShutdownHook(new ServerShutdownThread(this));
85 // CraftBukkit end
86 }
87
88 private boolean init() throws UnknownHostException { // CraftBukkit - added throws UnknownHostException
89 this.consoleCommandHandler = new ConsoleCommandHandler(this);
90 ThreadCommandReader threadcommandreader = new ThreadCommandReader(this);
91
92 threadcommandreader.setDaemon(true);
93 threadcommandreader.start();
94 ConsoleLogManager.init(this); // CraftBukkit
95
96 // CraftBukkit start
97 System.setOut(new PrintStream(new LoggerOutputStream(log, Level.INFO), true));
98 System.setErr(new PrintStream(new LoggerOutputStream(log, Level.SEVERE), true));
99 // CraftBukkit end
100
101 //If Poseidon Config DEBUG is enabled, enable debug mode
102 if (options.has("debug-config")) {
103 log.info("[Poseidon] Configuration debug mode has been enabled. This will cause the poseidon.yml to be reloaded every time the server starts.");
104 PoseidonConfig.getInstance().resetConfig();
105 }
106
107 modLoaderSupport = PoseidonConfig.getInstance().getBoolean("settings.support.modloader.enable", false);
108
109 if (modLoaderSupport) {
110 log.info("[UberBukkit] ModLoaderMP support is enabled, but has been removed.");
111 }
112
113 log.info("Starting minecraft server... Accepting PVNs: " + String.join(", ", UberbukkitConfig.getInstance().getString("client.allowed_protocols.value", "14").split(",")));
114 if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
115 log.warning("**** NOT ENOUGH RAM!");
116 log.warning("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
117 }
118
119 log.info("Loading properties");
120 this.propertyManager = new PropertyManager(this.options); // CraftBukkit - CLI argument support
121 String s = this.propertyManager.getString("server-ip", "");
122
123 this.onlineMode = this.propertyManager.getBoolean("online-mode", false); //Project Poseidon - False by default
124 this.spawnAnimals = this.propertyManager.getBoolean("spawn-animals", true);
125 this.pvpMode = this.propertyManager.getBoolean("pvp", true);
126 this.allowFlight = this.propertyManager.getBoolean("allow-flight", false);
127 InetAddress inetaddress = null;
128
129 if (s.length() > 0) {
130 inetaddress = InetAddress.getByName(s);
131 }
132
133 int i = this.propertyManager.getInt("server-port", 25565);
134
135 log.info("Starting Minecraft server on " + (s.length() == 0 ? "*" : s) + ":" + i);
136
137 try {
138 this.networkListenThread = new NetworkListenThread(this, inetaddress, i);
139 } catch (Throwable ioexception) { // CraftBukkit - IOException -> Throwable
140 log.warning("**** FAILED TO BIND TO PORT!");
141 log.log(Level.WARNING, "The exception was: " + ioexception.toString());
142 log.warning("Perhaps a server is already running on that port?");
143 return false;
144 }
145
146 if (!this.onlineMode) {
147 log.warning("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
148 log.warning("The server will make no attempt to authenticate usernames. Beware.");
149 log.warning("While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose.");
150 log.warning("To change this, set \"online-mode\" to \"true\" in the server.settings file.");
151 }
152
153 this.serverConfigurationManager = new ServerConfigurationManager(this);
154 // CraftBukkit - removed trackers
155 long j = System.nanoTime();
156 String s1 = this.propertyManager.getString("level-name", "world");
157 String s2 = this.propertyManager.getString("level-seed", "");
158 long k = (new Random()).nextLong();
159
160 if (s2.length() > 0) {
161 try {
162 k = Long.parseLong(s2);
163 } catch (NumberFormatException numberformatexception) {
164 k = (long) s2.hashCode();
165 }
166 }
167
168 log.info("Preparing level \"" + s1 + "\"");
169 this.a(new WorldLoaderServer(new File(".")), s1, k);
170
171 //Project Poseidon Start
172 Poseidon.getServer().initializeServer();
173 //Project Poseidon End
174
175 // CraftBukkit start
176 long elapsed = System.nanoTime() - j;
177 String time = String.format("%.3fs", elapsed / 10000000000.0D);
178 log.info("Done (" + time + ")! For help, type \"help\" or \"?\"");
179
180 // log rotator process start.
181 if ((boolean) PoseidonConfig.getInstance().getConfigOption("settings.per-day-log-file.enabled") && (boolean) PoseidonConfig.getInstance().getConfigOption("settings.per-day-log-file.latest-log.enabled")) {
182 String latestLogFileName = "latest";
183 ServerLogRotator serverLogRotator = new ServerLogRotator(latestLogFileName);
184 serverLogRotator.start();
185 }
186
187 if (this.propertyManager.properties.containsKey("spawn-protection")) {
188 log.info("'spawn-protection' in server.properties has been moved to 'settings.spawn-radius' in bukkit.yml. I will move your config for you.");
189 this.server.setSpawnRadius(this.propertyManager.getInt("spawn-protection", 16));
190 this.propertyManager.properties.remove("spawn-protection");
191 this.propertyManager.savePropertiesFile();
192 }
193 return true;
194 }
195
196 public boolean isModloaderPresent() {
197 try {
198 Class.forName("net.minecraft.server.ModLoader");
199 return true;
200 } catch (ClassNotFoundException e) {
201 return false;
202 }
203 }
204
205 private void a(Convertable convertable, String s, long i) {
206 if (convertable.isConvertable(s)) {
207 log.info("Converting map!");
208 convertable.convert(s, new ConvertProgressUpdater(this));
209 }
210
211 // CraftBukkit start
212 for (int j = 0; j < (this.propertyManager.getBoolean("allow-nether", true) ? 2 : 1); ++j) {
213 WorldServer world;
214 int dimension = j == 0 ? 0 : -1;
215 String worldType = Environment.getEnvironment(dimension).toString().toLowerCase();
216 String name = (dimension == 0) ? s : s + "_" + worldType;
217
218 ChunkGenerator gen = this.server.getGenerator(name);
219
220 if (j == 0) {
221 world = new WorldServer(this, new ServerNBTManager(new File("."), s, true), s, dimension, i, org.bukkit.World.Environment.getEnvironment(dimension), gen); // CraftBukkit
222 } else {
223 String dim = "DIM-1";
224
225 File newWorld = new File(new File(name), dim);
226 File oldWorld = new File(new File(s), dim);
227
228 if ((!newWorld.isDirectory()) && (oldWorld.isDirectory())) {
229 log.info("---- Migration of old " + worldType + " folder required ----");
230 log.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
231 log.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
232 log.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
233
234 if (newWorld.exists()) {
235 log.severe("A file or folder already exists at " + newWorld + "!");
236 log.info("---- Migration of old " + worldType + " folder failed ----");
237 } else if (newWorld.getParentFile().mkdirs()) {
238 if (oldWorld.renameTo(newWorld)) {
239 log.info("Success! To restore the nether in the future, simply move " + newWorld + " to " + oldWorld);
240 log.info("---- Migration of old " + worldType + " folder complete ----");
241 } else {
242 log.severe("Could not move folder " + oldWorld + " to " + newWorld + "!");
243 log.info("---- Migration of old " + worldType + " folder failed ----");
244 }
245 } else {
246 log.severe("Could not create path for " + newWorld + "!");
247 log.info("---- Migration of old " + worldType + " folder failed ----");
248 }
249 }
250
251 world = new SecondaryWorldServer(this, new ServerNBTManager(new File("."), name, true), name, dimension, i, this.worlds.get(0), org.bukkit.World.Environment.getEnvironment(dimension), gen); // CraftBukkit
252 }
253
254 if (gen != null) {
255 world.getWorld().getPopulators().addAll(gen.getDefaultPopulators(world.getWorld()));
256 }
257
258 this.server.getPluginManager().callEvent(new WorldInitEvent(world.getWorld()));
259
260 world.tracker = new EntityTracker(this, dimension);
261 world.addIWorldAccess(new WorldManager(this, world));
262 world.spawnMonsters = this.propertyManager.getBoolean("spawn-monsters", true) ? 1 : 0;
263 world.setSpawnFlags(this.propertyManager.getBoolean("spawn-monsters", true), this.spawnAnimals);
264 this.worlds.add(world);
265 this.serverConfigurationManager.setPlayerFileData(this.worlds.toArray(new WorldServer[0]));
266 }
267 // CraftBukkit end
268
269 short short1 = 196;
270 long k = System.currentTimeMillis();
271
272 // CraftBukkit start
273 for (int l = 0; l < this.worlds.size(); ++l) {
274 // if (l == 0 || this.propertyManager.getBoolean("allow-nether", true)) {
275 WorldServer worldserver = this.worlds.get(l);
276 log.info("Preparing start region for level " + l + " (Seed: " + worldserver.getSeed() + ")");
277 if (worldserver.getWorld().getKeepSpawnInMemory()) {
278 // CraftBukkit end
279 ChunkCoordinates chunkcoordinates = worldserver.getSpawn();
280
281 for (int i1 = -short1; i1 <= short1 && this.isRunning; i1 += 16) {
282 for (int j1 = -short1; j1 <= short1 && this.isRunning; j1 += 16) {
283 long k1 = System.currentTimeMillis();
284
285 if (k1 < k) {
286 k = k1;
287 }
288
289 if (k1 > k + 1000L) {
290 int l1 = (short1 * 2 + 1) * (short1 * 2 + 1);
291 int i2 = (i1 + short1) * (short1 * 2 + 1) + j1 + 1;
292
293 this.a("Preparing spawn area", i2 * 100 / l1);
294 k = k1;
295 }
296
297 worldserver.chunkProviderServer.getChunkAt(chunkcoordinates.x + i1 >> 4, chunkcoordinates.z + j1 >> 4);
298
299 while (worldserver.doLighting() && this.isRunning) {
300 ;
301 }
302 }
303 }
304 } // CraftBukkit
305 }
306
307 // CraftBukkit start
308 for (World world : this.worlds) {
309 this.server.getPluginManager().callEvent(new WorldLoadEvent(world.getWorld()));
310 }
311 // CraftBukkit end
312
313 this.e();
314 }
315
316 private void a(String s, int i) {
317 this.i = s;
318 this.j = i;
319 log.info(s + ": " + i + "%");
320 }
321
322 private void e() {
323 this.i = null;
324 this.j = 0;
325
326 this.server.enablePlugins(PluginLoadOrder.POSTWORLD); // CraftBukkit
327 }
328
329 void saveChunks() { // CraftBukkit - private -> default
330 log.info("Saving chunks");
331
332 // CraftBukkit start
333 for (int i = 0; i < this.worlds.size(); ++i) {
334 WorldServer worldserver = this.worlds.get(i);
335
336 worldserver.save(true, (IProgressUpdate) null);
337 worldserver.saveLevel();
338
339 WorldSaveEvent event = new WorldSaveEvent(worldserver.getWorld());
340 this.server.getPluginManager().callEvent(event);
341 }
342
343 WorldServer world = this.worlds.get(0);
344 if (!world.canSave) {
345 this.serverConfigurationManager.savePlayers();
346 }
347 // CraftBukkit end
348 }
349
350 public void stop() { // CraftBukkit - private -> public
351 log.info("Stopping server");
352
353 //Project Poseidon Start
354
355 // This is done before disablePlugins() to ensure the watchdog doesn't detect plugins disabling as a server hang
356 Poseidon.getServer().shutdownServer();
357
358 CrackedAllowlist.get().saveAllowlist();
359 //Project Poseidon End
360
361 // CraftBukkit start
362 if (this.server != null) {
363 this.server.disablePlugins();
364 }
365 // CraftBukkit end
366
367 if (this.serverConfigurationManager != null) {
368 this.serverConfigurationManager.savePlayers();
369 }
370
371 // CraftBukkit start - multiworld is handled in saveChunks() already.
372 WorldServer worldserver = this.worlds.get(0);
373
374 if (worldserver != null) {
375 this.saveChunks();
376 }
377 // CraftBukkit end
378 }
379
380 public void a() {
381 this.isRunning = false;
382 }
383
384 public void run() {
385 try {
386 if (this.init()) {
387 long i = System.currentTimeMillis();
388
389 for (long j = 0L; this.isRunning; Thread.sleep(1L)) {
390 long k = System.currentTimeMillis();
391 long l = k - i;
392
393 if (l > 2000L) {
394 log.warning("Can\'t keep up! Did the system time change, or is the server overloaded?");
395 l = 2000L;
396 }
397
398 if (l < 0L) {
399 log.warning("Time ran backwards! Did the system time change?");
400 l = 0L;
401 }
402
403 j += l;
404 i = k;
405 if (this.worlds.get(0).everyoneDeeplySleeping()) { // CraftBukkit
406 this.h();
407 j = 0L;
408 } else {
409 while (j > 50L) {
410 MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
411 getWatchdog().tickUpdate(); // Project Poseidon
412 j -= 50L;
413 this.h();
414 }
415 }
416 }
417 } else {
418 while (this.isRunning) {
419 this.b();
420
421 try {
422 Thread.sleep(10L);
423 } catch (InterruptedException interruptedexception) {
424 interruptedexception.printStackTrace();
425 }
426 }
427 }
428 } catch (Throwable throwable) {
429 throwable.printStackTrace();
430 log.log(Level.SEVERE, "Unexpected exception", throwable);
431
432 while (this.isRunning) {
433 this.b();
434
435 try {
436 Thread.sleep(10L);
437 } catch (InterruptedException interruptedexception1) {
438 interruptedexception1.printStackTrace();
439 }
440 }
441 } finally {
442 try {
443 this.stop();
444 this.isStopped = true;
445 } catch (Throwable throwable1) {
446 throwable1.printStackTrace();
447 } finally {
448 System.exit(0);
449 }
450 }
451 }
452
453 //Project Poseidon Start - Tick Update
454 private final LinkedList<Double> tpsRecords = new LinkedList<>();
455 private long lastTick = System.currentTimeMillis();
456 private int tickCount = 0;
457
458 public LinkedList<Double> getTpsRecords() {
459 return tpsRecords;
460 }
461 //Project Poseidon End - Tick Update
462
463 private void h() {
464 ArrayList arraylist = new ArrayList();
465 Iterator iterator = trackerList.keySet().iterator();
466
467 while (iterator.hasNext()) {
468 String s = (String) iterator.next();
469 int i = ((Integer) trackerList.get(s)).intValue();
470
471 if (i > 0) {
472 trackerList.put(s, Integer.valueOf(i - 1));
473 } else {
474 arraylist.add(s);
475 }
476 }
477
478 int j;
479
480 for (j = 0; j < arraylist.size(); ++j) {
481 trackerList.remove(arraylist.get(j));
482 }
483
484 AxisAlignedBB.a();
485 Vec3D.a();
486 ++this.ticks;
487
488 ((CraftScheduler) this.server.getScheduler()).mainThreadHeartbeat(this.ticks); // CraftBukkit
489
490 //Project Poseidon Start - Tick Update
491 long currentTime = System.currentTimeMillis();
492 tickCount++;
493
494 //Check if a second has passed
495 if (currentTime - lastTick >= 1000) {
496 double tps = tickCount / ((currentTime - lastTick) / 1000.0);
497 tpsRecords.addFirst(tps);
498 if (tpsRecords.size() > 900) { //Don't keep more than 15 minutes of data
499 tpsRecords.removeLast();
500 }
501
502 tickCount = 0;
503 lastTick = currentTime;
504 }
505
506 //Project Poseidon End - Tick Update
507
508
509 for (j = 0; j < this.worlds.size(); ++j) { // CraftBukkit
510 // if (j == 0 || this.propertyManager.getBoolean("allow-nether", true)) { // CraftBukkit
511 WorldServer worldserver = this.worlds.get(j); // CraftBukkit
512
513 if (this.ticks % 20 == 0) {
514 // CraftBukkit start - only send timeupdates to the people in that world
515 for (int i = 0; i < worldserver.players.size(); ++i) { // Project Poseidon: serverConfigurationManager -> worldserver.players
516 EntityPlayer entityPlayer = (EntityPlayer) worldserver.players.get(i);
517 if (entityPlayer != null) {
518 entityPlayer.netServerHandler.sendPacket(new Packet4UpdateTime(entityPlayer.getPlayerTime())); // Add support for per player time
519
520 }
521 }
522 // CraftBukkit end
523 }
524
525 worldserver.doTick();
526
527 while (worldserver.doLighting()) {
528 ;
529 }
530
531 worldserver.cleanUp();
532 }
533 // } // CraftBukkit
534
535 this.networkListenThread.a();
536 this.serverConfigurationManager.b();
537
538 // CraftBukkit start
539 for (j = 0; j < this.worlds.size(); ++j) {
540 this.worlds.get(j).tracker.updatePlayers();
541 }
542 // CraftBukkit end
543
544 for (j = 0; j < this.r.size(); ++j) {
545 ((IUpdatePlayerListBox) this.r.get(j)).a();
546 }
547
548 try {
549 this.b();
550 } catch (Exception exception) {
551 log.log(Level.WARNING, "Unexpected exception while parsing console command", exception);
552 }
553 }
554
555 public void issueCommand(String s, ICommandListener icommandlistener) {
556 this.s.add(new ServerCommand(s, icommandlistener));
557 }
558
559 public void b() {
560 while (this.s.size() > 0) {
561 ServerCommand servercommand = (ServerCommand) this.s.remove(0);
562
563 // CraftBukkit start - ServerCommand for preprocessing
564 ServerCommandEvent event = new ServerCommandEvent(this.console, servercommand.command);
565 this.server.getPluginManager().callEvent(event);
566 servercommand = new ServerCommand(event.getCommand(), servercommand.b);
567 // CraftBukkit end
568
569 // this.consoleCommandHandler.handle(servercommand); // CraftBukkit - Removed its now called in server.dispatchCommand
570 this.server.dispatchCommand(this.console, servercommand); // CraftBukkit
571 }
572 }
573
574 public void a(IUpdatePlayerListBox iupdateplayerlistbox) {
575 this.r.add(iupdateplayerlistbox);
576 }
577
578 public static void main(final OptionSet options) { // CraftBukkit - replaces main(String args[])
579 StatisticList.a();
580
581 try {
582 MinecraftServer minecraftserver = new MinecraftServer(options); // CraftBukkit - pass in the options
583
584 // CraftBukkit - remove gui
585
586 (new ThreadServerApplication("Server thread", minecraftserver)).start();
587 } catch (Exception exception) {
588 log.log(Level.SEVERE, "Failed to start the minecraft server", exception);
589 }
590 }
591
592 public File a(String s) {
593 return new File(s);
594 }
595
596 public void sendMessage(String s) {
597 log.info(s);
598 }
599
600 public void c(String s) {
601 log.warning(s);
602 }
603
604 public String getName() {
605 return "CONSOLE";
606 }
607
608 public WorldServer getWorldServer(int i) {
609 // CraftBukkit start
610 for (WorldServer world : this.worlds) {
611 if (world.dimension == i) {
612 return world;
613 }
614 }
615
616 return this.worlds.get(0);
617 // CraftBukkit end
618 }
619
620 public EntityTracker getTracker(int i) {
621 return this.getWorldServer(i).tracker; // CraftBukkit
622 }
623
624 public static boolean isRunning(MinecraftServer minecraftserver) {
625 return minecraftserver.isRunning;
626 }
627
628 public WatchDogThread getWatchdog() {
629 return Poseidon.getServer().getWatchDogThread();
630 }
631}