/src/main/java/net/glowstone/GlowServer.java
Java | 1320 lines | 819 code | 129 blank | 372 comment | 123 complexity | 61a80b4ea90235384b5c238aca6ff206 MD5 | raw file
1package net.glowstone;
2
3import com.flowpowered.network.Message;
4import com.google.common.base.Preconditions;
5import com.google.common.collect.Iterators;
6import com.jogamp.opencl.CLDevice;
7import com.jogamp.opencl.CLPlatform;
8import io.netty.channel.epoll.Epoll;
9import lombok.Getter;
10import net.glowstone.advancement.GlowAdvancement;
11import net.glowstone.advancement.GlowAdvancementDisplay;
12import net.glowstone.block.BuiltinMaterialValueManager;
13import net.glowstone.block.MaterialValueManager;
14import net.glowstone.block.entity.state.GlowDispenser;
15import net.glowstone.boss.BossBarManager;
16import net.glowstone.boss.GlowBossBar;
17import net.glowstone.client.GlowClient;
18import net.glowstone.command.glowstone.ColorCommand;
19import net.glowstone.command.glowstone.GlowstoneCommand;
20import net.glowstone.command.minecraft.*;
21import net.glowstone.constants.GlowEnchantment;
22import net.glowstone.constants.GlowPotionEffect;
23import net.glowstone.entity.EntityIdManager;
24import net.glowstone.entity.GlowPlayer;
25import net.glowstone.entity.meta.profile.PlayerProfile;
26import net.glowstone.generator.*;
27import net.glowstone.inventory.GlowInventory;
28import net.glowstone.inventory.GlowItemFactory;
29import net.glowstone.inventory.crafting.CraftingManager;
30import net.glowstone.io.PlayerDataService;
31import net.glowstone.io.PlayerStatisticIoService;
32import net.glowstone.io.ScoreboardIoService;
33import net.glowstone.map.GlowMapView;
34import net.glowstone.net.GameServer;
35import net.glowstone.net.SessionRegistry;
36import net.glowstone.net.message.play.player.AdvancementsMessage;
37import net.glowstone.net.query.QueryServer;
38import net.glowstone.net.rcon.RconServer;
39import net.glowstone.scheduler.GlowScheduler;
40import net.glowstone.scheduler.WorldScheduler;
41import net.glowstone.scoreboard.GlowScoreboardManager;
42import net.glowstone.util.*;
43import net.glowstone.util.bans.GlowBanList;
44import net.glowstone.util.bans.UuidListFile;
45import net.glowstone.util.config.ServerConfig;
46import net.glowstone.util.config.ServerConfig.Key;
47import net.glowstone.util.config.WorldConfig;
48import net.glowstone.util.loot.LootingManager;
49import net.md_5.bungee.api.chat.BaseComponent;
50import org.bukkit.*;
51import org.bukkit.BanList.Type;
52import org.bukkit.Warning.WarningState;
53import org.bukkit.World.Environment;
54import org.bukkit.advancement.Advancement;
55import org.bukkit.boss.BarColor;
56import org.bukkit.boss.BarFlag;
57import org.bukkit.boss.BarStyle;
58import org.bukkit.boss.BossBar;
59import org.bukkit.command.*;
60import org.bukkit.configuration.ConfigurationSection;
61import org.bukkit.configuration.MemorySection;
62import org.bukkit.configuration.serialization.ConfigurationSerialization;
63import org.bukkit.entity.Entity;
64import org.bukkit.entity.Player;
65import org.bukkit.event.inventory.InventoryType;
66import org.bukkit.generator.ChunkGenerator;
67import org.bukkit.generator.ChunkGenerator.ChunkData;
68import org.bukkit.help.HelpMap;
69import org.bukkit.inventory.*;
70import org.bukkit.permissions.Permissible;
71import org.bukkit.permissions.Permission;
72import org.bukkit.permissions.PermissionDefault;
73import org.bukkit.plugin.*;
74import org.bukkit.plugin.java.JavaPluginLoader;
75import org.bukkit.plugin.messaging.Messenger;
76import org.bukkit.plugin.messaging.StandardMessenger;
77import org.bukkit.util.CachedServerIcon;
78import org.bukkit.util.permissions.DefaultPermissions;
79
80import java.awt.image.BufferedImage;
81import java.io.File;
82import java.io.IOException;
83import java.net.InetSocketAddress;
84import java.nio.file.*;
85import java.nio.file.attribute.BasicFileAttributes;
86import java.security.KeyPair;
87import java.util.*;
88import java.util.concurrent.CountDownLatch;
89import java.util.logging.Level;
90import java.util.logging.Logger;
91import java.util.stream.Collectors;
92
93/**
94 * The core class of the Glowstone server.
95 *
96 * @author Graham Edgecombe
97 */
98public final class GlowServer implements Server {
99
100 /**
101 * The logger for this class.
102 */
103 public static final Logger logger = Logger.getLogger("Minecraft");
104 /**
105 * The game version supported by the server.
106 */
107 public static final String GAME_VERSION = "1.12.2";
108 /**
109 * The protocol version supported by the server.
110 */
111 public static final int PROTOCOL_VERSION = 340;
112 /**
113 * A list of all the active {@link net.glowstone.net.GlowSession}s.
114 */
115 private final SessionRegistry sessions = new SessionRegistry();
116 /**
117 * The console manager of this server.
118 */
119 private final ConsoleManager consoleManager = new ConsoleManager(this);
120 /**
121 * The services manager of this server.
122 */
123 private final SimpleServicesManager servicesManager = new SimpleServicesManager();
124 /**
125 * The command map of this server.
126 */
127 private final SimpleCommandMap commandMap = new SimpleCommandMap(this);
128 /**
129 * The plugin manager of this server.
130 */
131 private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
132 /**
133 * The plugin channel messenger for the server.
134 */
135 private final Messenger messenger = new StandardMessenger();
136 /**
137 * The help map for the server.
138 */
139 private final GlowHelpMap helpMap = new GlowHelpMap(this);
140 /**
141 * The scoreboard manager for the server.
142 */
143 private final GlowScoreboardManager scoreboardManager = new GlowScoreboardManager(this);
144 /**
145 * The crafting manager for this server.
146 */
147 private final CraftingManager craftingManager = new CraftingManager();
148 /**
149 * The configuration for the server.
150 */
151 private final ServerConfig config;
152 /**
153 * The world config for extended world customization.
154 */
155 private static WorldConfig worldConfig;
156 /**
157 * The list of OPs on the server.
158 */
159 private final UuidListFile opsList;
160 /**
161 * The list of players whitelisted on the server.
162 */
163 private final UuidListFile whitelist;
164 /**
165 * The BanList for player names.
166 */
167 private final GlowBanList nameBans;
168 /**
169 * The BanList for IP addresses.
170 */
171 private final GlowBanList ipBans;
172 /**
173 * The EntityIdManager for this server.
174 */
175 private final EntityIdManager entityIdManager = new EntityIdManager();
176 /**
177 * The world this server is managing.
178 */
179 private final WorldScheduler worlds = new WorldScheduler();
180 /**
181 * The task scheduler used by this server.
182 */
183 private final GlowScheduler scheduler = new GlowScheduler(this, worlds);
184 /**
185 * The Bukkit UnsafeValues implementation.
186 */
187 private final UnsafeValues unsafeAccess = new GlowUnsafeValues();
188 /**
189 * A RSA key pair used for encryption and authentication
190 */
191 private final KeyPair keyPair = SecurityUtils.generateKeyPair();
192 /**
193 * The network server used for network communication
194 */
195 @Getter
196 private GameServer networkServer;
197 /**
198 * A set of all online players.
199 */
200 private final Set<GlowPlayer> onlinePlayers = new HashSet<>();
201 /**
202 * A view of all online players.
203 */
204 private final Set<GlowPlayer> onlineView = Collections.unmodifiableSet(onlinePlayers);
205 /**
206 * The plugin type detector of thi server.
207 */
208 private GlowPluginTypeDetector pluginTypeDetector;
209 /**
210 * The server's default game mode
211 */
212 private GameMode defaultGameMode = GameMode.CREATIVE;
213
214 /**
215 * The setting for verbose deprecation warnings.
216 */
217 private WarningState warnState = WarningState.DEFAULT;
218
219 /**
220 * Whether the server is shutting down
221 */
222 private boolean isShuttingDown;
223
224 /**
225 * Whether the whitelist is in effect.
226 */
227 private boolean whitelistEnabled;
228
229 /**
230 * The size of the area to keep protected around the spawn point.
231 */
232 private int spawnRadius;
233
234 /**
235 * The ticks until a player who has not played the game has been kicked, or 0.
236 */
237 private int idleTimeout;
238 /**
239 * The query server for this server, or null if disabled.
240 */
241 private QueryServer queryServer;
242 /**
243 * The Rcon server for this server, or null if disabled.
244 */
245 private RconServer rconServer;
246 /**
247 * The default icon, usually blank, used for the server list.
248 */
249 private GlowServerIcon defaultIcon;
250 /**
251 * The server port.
252 */
253 private int port;
254 /**
255 * The server ip.
256 */
257 private String ip;
258 /**
259 * The {@link MaterialValueManager} of this server.
260 */
261 private MaterialValueManager materialValueManager;
262 /**
263 * The {@link BossBarManager} of this server.
264 */
265 private BossBarManager bossBarManager;
266 /**
267 * Default root permissions
268 */
269 public Permission permissionRoot, permissionCommand;
270 /**
271 * The {@link GlowAdvancement}s of this server.
272 */
273 private final Map<NamespacedKey, Advancement> advancements;
274
275 /**
276 * Creates a new server.
277 *
278 * @param config This server's config.
279 */
280 public GlowServer(ServerConfig config) {
281 materialValueManager = new BuiltinMaterialValueManager();
282 bossBarManager = new BossBarManager(this);
283 advancements = new HashMap<>();
284 // test advancement
285 GlowAdvancement advancement = new GlowAdvancement(NamespacedKey.minecraft("test"), null);
286 advancement.addCriterion("minecraft:test/criterion");
287 advancement.setDisplay(new GlowAdvancementDisplay(new TextMessage("Advancements in Glowstone"),
288 new TextMessage("=)"), new ItemStack(Material.GLOWSTONE), GlowAdvancementDisplay.FrameType.GOAL, -10F, 0));
289 addAdvancement(advancement);
290
291 this.config = config;
292 // stuff based on selected config directory
293 opsList = new UuidListFile(config.getFile("ops.json"));
294 whitelist = new UuidListFile(config.getFile("whitelist.json"));
295 nameBans = new GlowBanList(this, Type.NAME);
296 ipBans = new GlowBanList(this, Type.IP);
297
298 Bukkit.setServer(this);
299 loadConfig();
300 }
301
302 /**
303 * The client instance backed by this server.
304 */
305 public static GlowClient client;
306
307 /**
308 * Creates a new server on TCP port 25565 and starts listening for
309 * connections.
310 *
311 * @param args The command-line arguments.
312 */
313 public static void main(String... args) {
314 try {
315 GlowServer server = createFromArguments(args);
316
317 // we don't want to run a server when called with --version
318 if (server == null) {
319 return;
320 }
321
322 server.run();
323 } catch (SecurityException e) {
324 logger.log(Level.WARNING, "Error loading classpath!", e);
325 } catch (Throwable t) {
326 // general server startup crash
327 logger.log(Level.SEVERE, "Error during server startup.", t);
328 System.exit(1);
329 }
330 }
331
332 private static GlowServer createFromArguments(String... args) {
333 ServerConfig config = parseArguments(args);
334
335 // we don't want to create a server when called with --version
336 if (config == null) {
337 return null;
338 }
339
340 ConfigurationSerialization.registerClass(GlowOfflinePlayer.class);
341 GlowPotionEffect.register();
342 GlowEnchantment.register();
343 GlowDispenser.register();
344
345 return new GlowServer(config);
346 }
347
348 private static ServerConfig parseArguments(String... args) {
349 Map<Key, Object> parameters = new EnumMap<>(Key.class);
350 String configDirName = "config";
351 String configFileName = "glowstone.yml";
352
353 // Calculate acceptable parameters
354 for (int i = 0; i < args.length; i++) {
355 String opt = args[i];
356
357 if (opt.isEmpty() || opt.charAt(0) != '-') {
358 System.err.println("Ignored invalid option: " + opt);
359 continue;
360 }
361
362 // Help and version
363 if ("--help".equals(opt) || "-h".equals(opt) || "-?".equals(opt)) {
364 System.out.println("Available command-line options:");
365 System.out.println(" --help, -h, -? Shows this help message and exits.");
366 System.out.println(" --version, -v Shows version information and exits.");
367 System.out.println(" --configdir <directory> Sets the configuration directory.");
368 System.out.println(" --configfile <file> Sets the configuration file.");
369 System.out.println(" --port, -p <port> Sets the server listening port.");
370 System.out.println(" --host, -H <ip | hostname> Sets the server listening address.");
371 System.out.println(" --onlinemode, -o <onlinemode> Sets the server's online-mode.");
372 System.out.println(" --jline <true/false> Enables or disables JLine console.");
373 System.out.println(" --plugins-dir, -P <directory> Sets the plugin directory to use.");
374 System.out.println(" --worlds-dir, -W <directory> Sets the world directory to use.");
375 System.out.println(" --update-dir, -U <directory> Sets the plugin update folder to use.");
376 System.out.println(" --max-players, -M <director> Sets the maximum amount of players.");
377 System.out.println(" --world-name, -N <name> Sets the main world name.");
378 System.out.println(" --log-pattern, -L <pattern> Sets the log file pattern (%D for date).");
379 return null;
380 } else if ("--version".equals(opt) || "-v".equals(opt)) {
381 System.out.println("Glowstone version: " + GlowServer.class.getPackage().getImplementationVersion());
382 System.out.println("Bukkit version: " + GlowServer.class.getPackage().getSpecificationVersion());
383 System.out.println("Minecraft version: " + GAME_VERSION + " protocol " + PROTOCOL_VERSION);
384 return null;
385 }
386
387 // Below this point, options require parameters
388 if (i == args.length - 1) {
389 System.err.println("Ignored option specified without value: " + opt);
390 continue;
391 }
392
393 switch (opt) {
394 case "--configdir":
395 configDirName = args[++i];
396 break;
397 case "--configfile":
398 configFileName = args[++i];
399 break;
400 case "--port":
401 case "-p":
402 parameters.put(Key.SERVER_PORT, Integer.valueOf(args[++i]));
403 break;
404 case "--host":
405 case "-H":
406 parameters.put(Key.SERVER_IP, args[++i]);
407 break;
408 case "--onlinemode":
409 case "-o":
410 parameters.put(Key.ONLINE_MODE, Boolean.valueOf(args[++i]));
411 break;
412 case "--jline":
413 parameters.put(Key.USE_JLINE, Boolean.valueOf(args[++i]));
414 break;
415 case "--plugins-dir":
416 case "-P":
417 parameters.put(Key.PLUGIN_FOLDER, args[++i]);
418 break;
419 case "--worlds-dir":
420 case "-W":
421 parameters.put(Key.WORLD_FOLDER, args[++i]);
422 break;
423 case "--update-dir":
424 case "-U":
425 parameters.put(Key.UPDATE_FOLDER, args[++i]);
426 break;
427 case "--max-players":
428 case "-M":
429 parameters.put(Key.MAX_PLAYERS, Integer.valueOf(args[++i]));
430 break;
431 case "--world-name":
432 case "-N":
433 parameters.put(Key.LEVEL_NAME, args[++i]);
434 break;
435 case "--log-pattern":
436 case "-L":
437 parameters.put(Key.LOG_FILE, args[++i]);
438 break;
439 default:
440 System.err.println("Ignored invalid option: " + opt);
441 }
442 }
443
444 File configDir = new File(configDirName);
445 worldConfig = new WorldConfig(configDir, new File(configDir, "worlds.yml"));
446 File configFile = new File(configDir, configFileName);
447
448 return new ServerConfig(configDir, configFile, parameters);
449 }
450
451 /**
452 * Starts the server starting sequence (starting, binding to port, etc.)
453 */
454 public void run() {
455 start();
456 bind();
457 logger.info("Ready for connections.");
458
459 if (doMetrics()) {
460 new Metrics(this, config.getString(Key.METRICS_UUID), false);
461 }
462
463 if (config.getBoolean(Key.RUN_CLIENT)) {
464 client = new GlowClient(this);
465 client.run();
466 }
467 }
468
469 /**
470 * Whether OpenCL is to be used by the server on this run.
471 */
472 private boolean isCLApplicable = true;
473
474 /**
475 * Starts this server.
476 */
477 public void start() {
478 // Determine console mode and start reading input
479 consoleManager.startConsole(config.getBoolean(Key.USE_JLINE));
480 consoleManager.startFile(config.getString(Key.LOG_FILE));
481
482 if (getProxySupport()) {
483 if (getOnlineMode()) {
484 logger.warning("Proxy support is enabled, but online mode is enabled.");
485 } else {
486 logger.info("Proxy support is enabled.");
487 }
488 } else if (!getOnlineMode()) {
489 logger.warning("The server is running in offline mode! Only do this if you know what you're doing.");
490 }
491
492 int openCLMajor = 1;
493 int openCLMinor = 2;
494
495 if (doesUseGPGPU()) {
496 int maxGpuFlops = 0;
497 int maxIntelFlops = 0;
498 int maxCpuFlops = 0;
499 CLPlatform bestPlatform = null;
500 CLPlatform bestIntelPlatform = null;
501 CLPlatform bestCpuPlatform = null;
502 // gets the max flops device across platforms on the computer
503 for (CLPlatform platform : CLPlatform.listCLPlatforms()) {
504 if (platform.isAtLeast(openCLMajor, openCLMinor) && platform.isExtensionAvailable("cl_khr_fp64")) {
505 for (CLDevice device : platform.listCLDevices()) {
506 if (device.getType() == CLDevice.Type.GPU) {
507 int flops = device.getMaxComputeUnits() * device.getMaxClockFrequency();
508 logger.info("Found " + device + " with " + flops + " flops");
509 if (device.getVendor().contains("Intel")) {
510 if (flops > maxIntelFlops) {
511 maxIntelFlops = flops;
512 logger.info("Device is best platform so far, on " + platform);
513 bestIntelPlatform = platform;
514 } else if (flops == maxIntelFlops) {
515 if (bestIntelPlatform != null && bestIntelPlatform.getVersion().compareTo(platform.getVersion()) < 0) {
516 maxIntelFlops = flops;
517 logger.info("Device tied for flops, but had higher version on " + platform);
518 bestIntelPlatform = platform;
519 }
520 }
521 } else {
522 if (flops > maxGpuFlops) {
523 maxGpuFlops = flops;
524 logger.info("Device is best platform so far, on " + platform);
525 bestPlatform = platform;
526 } else if (flops == maxGpuFlops) {
527 if (bestPlatform != null && bestPlatform.getVersion().compareTo(platform.getVersion()) < 0) {
528 maxGpuFlops = flops;
529 logger.info("Device tied for flops, but had higher version on " + platform);
530 bestPlatform = platform;
531 }
532 }
533 }
534 } else {
535 int flops = device.getMaxComputeUnits() * device.getMaxClockFrequency();
536 logger.info("Found " + device + " with " + flops + " flops");
537 if (flops > maxCpuFlops) {
538 maxCpuFlops = flops;
539 logger.info("Device is best platform so far, on " + platform);
540 bestCpuPlatform = platform;
541 } else if (flops == maxCpuFlops) {
542 if (bestCpuPlatform != null && bestCpuPlatform.getVersion().compareTo(platform.getVersion()) < 0) {
543 maxCpuFlops = flops;
544 logger.info("Device tied for flops, but had higher version on " + platform);
545 bestCpuPlatform = platform;
546 }
547 }
548 }
549 }
550 }
551 }
552
553 if (config.getBoolean(Key.GPGPU_ANY_DEVICE)) {
554 if (maxGpuFlops - maxIntelFlops < 0 && maxCpuFlops - maxIntelFlops <= 0) {
555 bestPlatform = bestIntelPlatform;
556 } else if (maxGpuFlops - maxCpuFlops < 0 && maxIntelFlops - maxCpuFlops < 0) {
557 bestPlatform = bestCpuPlatform;
558 }
559 } else {
560 if (maxGpuFlops == 0) {
561 if (maxIntelFlops == 0) {
562 logger.info("No Intel graphics found, best platform is the best CPU platform we could find...");
563 bestPlatform = bestCpuPlatform;
564 } else {
565 logger.info("No dGPU found, best platform is the best Intel graphics we could find...");
566 bestPlatform = bestIntelPlatform;
567 }
568 }
569 }
570
571 if (bestPlatform == null) {
572 isCLApplicable = false;
573 logger.info("Your system does not meet the OpenCL requirements for Glowstone. See if driver updates are available.");
574 logger.info("Required version: " + openCLMajor + '.' + openCLMinor);
575 logger.info("Required extensions: [ cl_khr_fp64 ]");
576 } else {
577 OpenCL.initContext(bestPlatform);
578 }
579 }
580
581 // Load player lists
582 opsList.load();
583 whitelist.load();
584 nameBans.load();
585 ipBans.load();
586 setPort(config.getInt(Key.SERVER_PORT));
587 setIp(config.getString(Key.SERVER_IP));
588
589 try {
590 LootingManager.load();
591 } catch (Exception e) {
592 GlowServer.logger.severe("Failed to load looting manager: ");
593 e.printStackTrace();
594 }
595
596 // Start loading plugins
597 new LibraryManager().run();
598 loadPlugins();
599 enablePlugins(PluginLoadOrder.STARTUP);
600
601 // Create worlds
602 String name = config.getString(Key.LEVEL_NAME);
603 String seedString = config.getString(Key.LEVEL_SEED);
604 boolean structs = getGenerateStructures();
605 WorldType type = WorldType.getByName(getWorldType());
606 if (type == null) {
607 type = WorldType.NORMAL;
608 }
609
610 long seed = new Random().nextLong();
611 if (!seedString.isEmpty()) {
612 try {
613 long parsed = Long.parseLong(seedString);
614 if (parsed != 0) {
615 seed = parsed;
616 }
617 } catch (NumberFormatException ex) {
618 seed = seedString.hashCode();
619 }
620 }
621
622 createWorld(WorldCreator.name(name).environment(Environment.NORMAL).seed(seed).type(type).generateStructures(structs));
623 if (getAllowNether()) {
624 checkTransfer(name, "_nether", Environment.NETHER);
625 createWorld(WorldCreator.name(name + "_nether").environment(Environment.NETHER).seed(seed).type(type).generateStructures(structs));
626 }
627 if (getAllowEnd()) {
628 checkTransfer(name, "_the_end", Environment.THE_END);
629 createWorld(WorldCreator.name(name + "_the_end").environment(Environment.THE_END).seed(seed).type(type).generateStructures(structs));
630 }
631
632 // Finish loading plugins
633 enablePlugins(PluginLoadOrder.POSTWORLD);
634 commandMap.registerServerAliases();
635 scheduler.start();
636 }
637
638 private void checkTransfer(String name, String suffix, Environment environment) {
639 // todo: import things like per-dimension villages.dat when those are implemented
640 Path srcPath = new File(new File(getWorldContainer(), name), "DIM" + environment.getId()).toPath();
641 Path destPath = new File(getWorldContainer(), name + suffix).toPath();
642 if (Files.exists(srcPath) && !Files.exists(destPath)) {
643 logger.info("Importing " + destPath + " from " + srcPath);
644 try {
645 Files.walkFileTree(srcPath, new FileVisitor<Path>() {
646 @Override
647 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
648 Path target = destPath.resolve(srcPath.relativize(dir));
649 if (!Files.exists(target)) {
650 Files.createDirectory(target);
651 }
652 return FileVisitResult.CONTINUE;
653 }
654
655 @Override
656 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
657 Files.copy(file, destPath.resolve(srcPath.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES);
658 return FileVisitResult.CONTINUE;
659 }
660
661 @Override
662 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
663 logger.warning("Importing file " + srcPath.relativize(file) + " + failed: " + exc);
664 return FileVisitResult.CONTINUE;
665 }
666
667 @Override
668 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
669 return FileVisitResult.CONTINUE;
670 }
671 });
672 Files.copy(srcPath.resolve("../level.dat"), destPath.resolve("level.dat"));
673 } catch (IOException e) {
674 logger.log(Level.WARNING, "Import of " + srcPath + " failed", e);
675 }
676 }
677 }
678
679 private void bind() {
680 if (Epoll.isAvailable()) {
681 logger.info("Native epoll transport is enabled.");
682 }
683
684 CountDownLatch latch = new CountDownLatch(3);
685
686 networkServer = new GameServer(this, latch);
687 networkServer.bind(getBindAddress(Key.SERVER_PORT));
688
689 if (config.getBoolean(Key.QUERY_ENABLED)) {
690 queryServer = new QueryServer(this, latch, config.getBoolean(Key.QUERY_PLUGINS));
691 queryServer.bind(getBindAddress(Key.QUERY_PORT));
692 } else {
693 latch.countDown();
694 }
695
696 if (config.getBoolean(Key.RCON_ENABLED)) {
697 rconServer = new RconServer(this, latch, config.getString(Key.RCON_PASSWORD));
698 rconServer.bind(getBindAddress(Key.RCON_PORT));
699 } else {
700 latch.countDown();
701 }
702
703 try {
704 latch.await();
705 } catch (InterruptedException e) {
706 logger.log(Level.SEVERE, "Bind interrupted! ", e);
707 System.exit(1);
708 }
709 }
710
711 /**
712 * Sets the port that the Query server will expose.
713 *
714 * <p>This does not change the port the server will run on.
715 *
716 * @param port the port number
717 */
718 public void setPort(int port) {
719 this.port = port;
720 }
721
722 /**
723 * Sets the IP address that the Query server will expose.
724 *
725 * <p>This does not change the IP address the server will run on.
726 *
727 * @param ip the IP address
728 */
729 public void setIp(String ip) {
730 this.ip = ip;
731 }
732
733 /**
734 * Get the SocketAddress to bind to for a specified service.
735 *
736 * @param portKey The configuration key for the port to use.
737 * @return The SocketAddress
738 */
739 private InetSocketAddress getBindAddress(Key portKey) {
740 String ip = config.getString(Key.SERVER_IP);
741 int port = config.getInt(portKey);
742 if (ip.isEmpty()) {
743 return new InetSocketAddress(port);
744 } else {
745 return new InetSocketAddress(ip, port);
746 }
747 }
748
749 /**
750 * Stops this server.
751 */
752 @Override
753 public void shutdown() {
754 // Just in case this gets called twice
755 if (isShuttingDown) {
756 return;
757 }
758 isShuttingDown = true;
759 logger.info("The server is shutting down...");
760
761 // Disable plugins
762 pluginManager.clearPlugins();
763
764 // Kick all players (this saves their data too)
765 for (GlowPlayer player : new ArrayList<>(getRawOnlinePlayers())) {
766 player.kickPlayer(getShutdownMessage(), false);
767 }
768
769 // Stop the network servers - starts the shutdown process
770 // It may take a second or two for Netty to totally clean up
771 if (networkServer != null) {
772 networkServer.shutdown();
773 }
774 if (queryServer != null) {
775 queryServer.shutdown();
776 }
777 if (rconServer != null) {
778 rconServer.shutdown();
779 }
780
781 // Save worlds
782 for (World world : getWorlds()) {
783 logger.info("Saving world: " + world.getName());
784 unloadWorld(world, true);
785 }
786
787 // Stop scheduler and console
788 scheduler.stop();
789 consoleManager.stop();
790
791 // Wait for a while and terminate any rogue threads
792 new ShutdownMonitorThread().start();
793 }
794
795 /**
796 * Load the server configuration.
797 */
798 private void loadConfig() {
799 config.load();
800 worldConfig.load();
801
802 // modifiable values
803 spawnRadius = config.getInt(Key.SPAWN_RADIUS);
804 whitelistEnabled = config.getBoolean(Key.WHITELIST);
805 idleTimeout = config.getInt(Key.PLAYER_IDLE_TIMEOUT);
806 craftingManager.initialize();
807
808 // special handling
809 warnState = WarningState.value(config.getString(Key.WARNING_STATE));
810 try {
811 defaultGameMode = GameMode.valueOf(config.getString(Key.GAMEMODE));
812 } catch (IllegalArgumentException | NullPointerException e) {
813 defaultGameMode = GameMode.SURVIVAL;
814 }
815
816 // server icon
817 defaultIcon = new GlowServerIcon();
818 try {
819 File file = config.getFile("server-icon.png");
820 if (file.isFile()) {
821 defaultIcon = new GlowServerIcon(file);
822 }
823 } catch (Exception e) {
824 logger.log(Level.WARNING, "Failed to load server-icon.png", e);
825 }
826 }
827
828 /**
829 * Loads all plugins, calling onLoad, &c.
830 */
831 private void loadPlugins() {
832 // clear the map
833 commandMap.clearCommands();
834 // glowstone commands
835 commandMap.register("glowstone", new ColorCommand());
836 commandMap.register("glowstone", new GlowstoneCommand());
837 // vanilla commands
838 commandMap.register("minecraft", new TellrawCommand());
839 commandMap.register("minecraft", new TitleCommand());
840 commandMap.register("minecraft", new TeleportCommand());
841 commandMap.register("minecraft", new SummonCommand());
842 commandMap.register("minecraft", new WorldBorderCommand());
843 commandMap.register("minecraft", new SayCommand());
844 commandMap.register("minecraft", new StopCommand());
845 commandMap.register("minecraft", new OpCommand());
846 commandMap.register("minecraft", new GameModeCommand());
847 commandMap.register("minecraft", new FunctionCommand());
848 commandMap.register("minecraft", new DeopCommand());
849 commandMap.register("minecraft", new KickCommand());
850 commandMap.register("minecraft", new GameRuleCommand());
851 commandMap.register("minecraft", new TellCommand());
852 commandMap.register("minecraft", new ListCommand());
853 commandMap.register("minecraft", new BanCommand());
854 commandMap.register("minecraft", new BanIpCommand());
855 commandMap.register("minecraft", new BanListCommand());
856 commandMap.register("minecraft", new GiveCommand());
857 commandMap.register("minecraft", new DifficultyCommand());
858 commandMap.register("minecraft", new KillCommand());
859 commandMap.register("minecraft", new PardonCommand());
860 commandMap.register("minecraft", new PardonIpCommand());
861 commandMap.register("minecraft", new WhitelistCommand());
862 commandMap.register("minecraft", new TimeCommand());
863 commandMap.register("minecraft", new WeatherCommand());
864 commandMap.register("minecraft", new SaveAllCommand());
865 commandMap.register("minecraft", new SaveToggleCommand(true));
866 commandMap.register("minecraft", new SaveToggleCommand(false));
867 commandMap.register("minecraft", new ClearCommand());
868 commandMap.register("minecraft", new TpCommand());
869 commandMap.register("minecraft", new MeCommand());
870 commandMap.register("minecraft", new SeedCommand());
871 commandMap.register("minecraft", new XpCommand());
872 commandMap.register("minecraft", new DefaultGameModeCommand());
873 commandMap.register("minecraft", new SetIdleTimeoutCommand());
874 commandMap.register("minecraft", new SpawnPointCommand());
875 commandMap.register("minecraft", new ToggleDownfallCommand());
876 commandMap.register("minecraft", new SetWorldSpawnCommand());
877 commandMap.register("minecraft", new PlaySoundCommand());
878 commandMap.register("minecraft", new EffectCommand());
879 commandMap.register("minecraft", new EnchantCommand());
880 commandMap.register("minecraft", new TestForCommand());
881 commandMap.register("minecraft", new TestForBlockCommand());
882 commandMap.register("minecraft", new SetBlockCommand());
883
884 File folder = new File(config.getString(Key.PLUGIN_FOLDER));
885 if (!folder.isDirectory() && !folder.mkdirs()) {
886 logger.log(Level.SEVERE, "Could not create plugins directory: " + folder);
887 }
888
889 // detect plugin types
890 pluginTypeDetector = new GlowPluginTypeDetector(folder);
891 pluginTypeDetector.scan();
892
893 // clear plugins and prepare to load (Bukkit)
894 pluginManager.clearPlugins();
895 pluginManager.registerInterface(JavaPluginLoader.class);
896 Plugin[] plugins = pluginManager.loadPlugins(folder.getPath(), pluginTypeDetector.bukkitPlugins.toArray(new File[pluginTypeDetector.bukkitPlugins.size()]));
897
898 // call onLoad methods
899 for (Plugin plugin : plugins) {
900 try {
901 plugin.onLoad();
902 } catch (Exception ex) {
903 logger.log(Level.SEVERE, "Error loading " + plugin.getDescription().getFullName(), ex);
904 }
905 }
906
907 if (!pluginTypeDetector.spongePlugins.isEmpty()) {
908 boolean hasSponge = false;
909 for (Plugin plugin : plugins) {
910 if (plugin.getName().equals("Bukkit2Sponge")) {
911 hasSponge = true; // TODO: better detection method, plugin description file annotation APIs?
912 break;
913 }
914 }
915
916 boolean spongeOnlyPlugins = false;
917 for (File spongePlugin : pluginTypeDetector.spongePlugins) {
918 if (!pluginTypeDetector.bukkitPlugins.contains(spongePlugin)) {
919 spongeOnlyPlugins = true;
920 }
921 }
922
923 if (!hasSponge && spongeOnlyPlugins) {
924 logger.log(Level.WARNING, "SpongeAPI plugins found, but no Sponge bridge present! They will be ignored.");
925 for (File file : getSpongePlugins()) {
926 logger.log(Level.WARNING, "Ignored SpongeAPI plugin: " + file.getPath());
927 }
928 logger.log(Level.WARNING, "Suggestion: install https://github.com/GlowstoneMC/Bukkit2Sponge to load these plugins");
929 }
930 }
931
932 if (!pluginTypeDetector.canaryPlugins.isEmpty() ||
933 !pluginTypeDetector.forgefPlugins.isEmpty() ||
934 !pluginTypeDetector.forgenPlugins.isEmpty() ||
935 !pluginTypeDetector.unrecognizedPlugins.isEmpty()) {
936 logger.log(Level.WARNING, "Unsupported plugin types found, will be ignored:");
937
938 for (File file : pluginTypeDetector.canaryPlugins)
939 logger.log(Level.WARNING, "Canary plugin not supported: " + file.getPath());
940
941 for (File file : pluginTypeDetector.forgefPlugins)
942 logger.log(Level.WARNING, "Forge plugin not supported: " + file.getPath());
943
944 for (File file : pluginTypeDetector.forgenPlugins)
945 logger.log(Level.WARNING, "Forge plugin not supported: " + file.getPath());
946
947 for (File file : pluginTypeDetector.unrecognizedPlugins)
948 logger.log(Level.WARNING, "Unrecognized plugin not supported: " + file.getPath());
949 }
950
951 }
952
953 /**
954 * A list of detected files that are Sponge plugins.
955 *
956 * @return a list of {@link File Files} that are Sponge plugins.
957 */
958 public List<File> getSpongePlugins() {
959 return pluginTypeDetector.spongePlugins;
960 }
961
962 /**
963 * Enable all plugins of the given load order type.
964 *
965 * @param type The type of plugin to enable.
966 */
967 private void enablePlugins(PluginLoadOrder type) {
968 if (type == PluginLoadOrder.STARTUP) {
969 helpMap.clear();
970 helpMap.loadConfig(config.getConfigFile(Key.HELP_FILE));
971 }
972
973 // load all the plugins
974 Plugin[] plugins = pluginManager.getPlugins();
975 for (Plugin plugin : plugins) {
976 if (!plugin.isEnabled() && plugin.getDescription().getLoad() == type) {
977 List<Permission> perms = plugin.getDescription().getPermissions();
978 for (Permission perm : perms) {
979 try {
980 pluginManager.addPermission(perm);
981 } catch (IllegalArgumentException ex) {
982 getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
983 }
984 }
985
986 try {
987 pluginManager.enablePlugin(plugin);
988 } catch (Throwable ex) {
989 logger.log(Level.SEVERE, "Error loading " + plugin.getDescription().getFullName(), ex);
990 }
991 }
992 }
993
994 if (type == PluginLoadOrder.POSTWORLD) {
995 commandMap.setFallbackCommands();
996 commandMap.registerServerAliases();
997 DefaultPermissions.registerCorePermissions();
998 // Default permissions
999 this.permissionRoot = DefaultPermissions.registerPermission("minecraft", "Gives the user the ability to use all Minecraft utilities and commands");
1000 this.permissionCommand = DefaultPermissions.registerPermission("minecraft.command", "Gives the user the ability to use all Minecraft commands", permissionRoot);
1001 DefaultPermissions.registerPermission("minecraft.command.tell", "Allows the user to send a private message", PermissionDefault.TRUE, permissionCommand);
1002 permissionCommand.recalculatePermissibles();
1003 permissionRoot.recalculatePermissibles();
1004 helpMap.initializeCommands();
1005 helpMap.amendTopics(config.getConfigFile(Key.HELP_FILE));
1006
1007 // load permissions.yml
1008 ConfigurationSection permConfig = config.getConfigFile(Key.PERMISSIONS_FILE);
1009
1010 Map<String, Map<String, Object>> data = new HashMap<>();
1011
1012 permConfig.getValues(false).forEach((key, value) -> data.put(key, ((MemorySection) value).getValues(false)));
1013
1014 List<Permission> perms = Permission.loadPermissions(data, "Permission node '%s' in permissions config is invalid", PermissionDefault.OP);
1015
1016 for (Permission perm : perms) {
1017 try {
1018 pluginManager.addPermission(perm);
1019 } catch (IllegalArgumentException ex) {
1020 getLogger().log(Level.WARNING, "Permission config tried to register '" + perm.getName() + "' but it's already registered", ex);
1021 }
1022 }
1023 }
1024 }
1025
1026 /**
1027 * Reloads the server, refreshing settings and plugin information
1028 */
1029 @Override
1030 public void reload() {
1031 try {
1032 // Reload relevant configuration
1033 loadConfig();
1034 opsList.load();
1035 whitelist.load();
1036 nameBans.load();
1037 ipBans.load();
1038
1039 // Reset crafting
1040 craftingManager.resetRecipes();
1041
1042 // Load plugins
1043 loadPlugins();
1044 enablePlugins(PluginLoadOrder.STARTUP);
1045 enablePlugins(PluginLoadOrder.POSTWORLD);
1046 } catch (Exception ex) {
1047 logger.log(Level.SEVERE, "Uncaught error while reloading", ex);
1048 }
1049 }
1050
1051 @Override
1052 public void reloadData() {
1053
1054 }
1055
1056 @Override
1057 public String toString() {
1058 return "GlowServer{name=" + getName() + ",version=" + getVersion() + ",minecraftVersion=" + GAME_VERSION + "}";
1059 }
1060
1061 ////////////////////////////////////////////////////////////////////////////
1062 // Access to internals
1063
1064 /**
1065 * Gets the command map.
1066 *
1067 * @return The {@link SimpleCommandMap}.
1068 */
1069 public SimpleCommandMap getCommandMap() {
1070 return commandMap;
1071 }
1072
1073 @Override
1074 public Advancement getAdvancement(NamespacedKey namespacedKey) {
1075 return advancements.get(namespacedKey);
1076 }
1077
1078 @Override
1079 public Iterator<Advancement> advancementIterator() {
1080 return Iterators.cycle(advancements.values());
1081 }
1082
1083 /**
1084 * Registers an advancement to the advancement registry.
1085 *
1086 * @param advancement the advancement to add.
1087 */
1088 public void addAdvancement(Advancement advancement) {
1089 advancements.put(advancement.getKey(), advancement);
1090 }
1091
1092 /**
1093 * Creates an {@link AdvancementsMessage} containing a list of advancements the server has, along with some extra actions.
1094 *
1095 * <p>This does not affect the server's advancement registry.
1096 *
1097 * @param clear whether to clear the advancements on the player's perspective.
1098 * @param remove a list of advancement {@link NamespacedKey NamespacedKeys} to remove from the player's perspective.
1099 * @return a resulting {@link AdvancementsMessage} packet
1100 */
1101 public AdvancementsMessage createAdvancementsMessage(boolean clear, List<NamespacedKey> remove) {
1102 return createAdvancementsMessage(advancements, clear, remove);
1103 }
1104
1105 /**
1106 * Creates an {@link AdvancementsMessage} containing a given list of advancements, along with some extra actions.
1107 *
1108 * <p>This does not affect the server's advancement registry.
1109 *
1110 * @param advancements the advancements to add to the player's perspective.
1111 * @param clear whether to clear the advancements on the player's perspective.
1112 * @param remove a list of advancement {@link NamespacedKey NamespacedKeys} to remove from the player's perspective.
1113 * @return a resulting {@link AdvancementsMessage} packet
1114 */
1115 public AdvancementsMessage createAdvancementsMessage(Map<NamespacedKey, Advancement> advancements, boolean clear, List<NamespacedKey> remove) {
1116 return new AdvancementsMessage(clear, advancements, remove);
1117 }
1118
1119 /**
1120 * Gets the session registry.
1121 *
1122 * @return The {@link SessionRegistry}.
1123 */
1124 public SessionRegistry getSessionRegistry() {
1125 return sessions;
1126 }
1127
1128 /**
1129 * Gets the entity id manager.
1130 *
1131 * @return The {@link EntityIdManager}.
1132 */
1133 public EntityIdManager getEntityIdManager() {
1134 return entityIdManager;
1135 }
1136
1137 /**
1138 * Returns the list of operators on this server.
1139 *
1140 * @return A file containing a list of UUIDs for this server's operators.
1141 */
1142 public UuidListFile getOpsList() {
1143 return opsList;
1144 }
1145
1146 /**
1147 * Returns the list of whitelisted players on this server.
1148 *
1149 * @return A file containing a list of UUIDs for this server's whitelisted players.
1150 */
1151 public UuidListFile getWhitelist() {
1152 return whitelist;
1153 }
1154
1155 @Override
1156 public void setWhitelist(boolean enabled) {
1157 whitelistEnabled = enabled;
1158 config.set(Key.WHITELIST, whitelistEnabled);
1159 config.save();
1160 }
1161
1162 /**
1163 * Returns the folder where configuration files are stored
1164 *
1165 * @return The server's configuration folder.
1166 */
1167 public File getConfigDir() {
1168 return config.getDirectory();
1169 }
1170
1171 /**
1172 * Return the crafting manager.
1173 *
1174 * @return The server's crafting manager.
1175 */
1176 public CraftingManager getCraftingManager() {
1177 return craftingManager;
1178 }
1179
1180 /**
1181 * The key pair generated at server start up
1182 *
1183 * @return The key pair generated at server start up
1184 */
1185 public KeyPair getKeyPair() {
1186 return keyPair;
1187 }
1188
1189 /**
1190 * Returns the player data service attached to the first world.
1191 *
1192 * @return The server's player data service.
1193 */
1194 public PlayerDataService getPlayerDataService() {
1195 return worlds.getWorlds().get(0).getStorage().getPlayerDataService();
1196 }
1197
1198 /**
1199 * Returns the scoreboard I/O service attached to the first world.
1200 *
1201 * @return The server's scoreboard I/O service
1202 */
1203 public ScoreboardIoService getScoreboardIoService() {
1204 return worlds.getWorlds().get(0).getStorage().getScoreboardIoService();
1205 }
1206
1207 /**
1208 * Returns the player statitics I/O service attached to the first world.
1209 *
1210 * @return the server's statistics I/O service
1211 */
1212 public PlayerStatisticIoService getPlayerStatisticIoService() {
1213 return worlds.getWorlds().get(0).getStorage().getPlayerStatisticIoService();
1214 }
1215
1216 /**
1217 * Get the threshold to use for network compression defined in the config.
1218 *
1219 * @return The compression threshold, or -1 for no compression.
1220 */
1221 public int getCompressionThreshold() {
1222 return config.getInt(Key.COMPRESSION_THRESHOLD);
1223 }
1224
1225 /**
1226 * Get the default game difficulty defined in the config.
1227 *
1228 * @return The default difficulty.
1229 */
1230 public Difficulty getDifficulty() {
1231 try {
1232 return Difficulty.valueOf(config.getString(Key.DIFFICULTY));
1233 } catch (IllegalArgumentException | NullPointerException e) {
1234 return Difficulty.NORMAL;
1235 }
1236 }
1237
1238 /**
1239 * Get whether worlds should keep their spawns loaded by default.
1240 *
1241 * @return Whether to keep spawns loaded by default.
1242 */
1243 public boolean keepSpawnLoaded() {
1244 return config.getBoolean(Key.PERSIST_SPAWN);
1245 }
1246
1247 /**
1248 * Get whether to populate chunks when they are anchored.
1249 *
1250 * @return Whether to populate chunks when they are anchored.
1251 */
1252 public boolean populateAnchoredChunks() {
1253 return config.getBoolean(Key.POPULATE_ANCHORED_CHUNKS);
1254 }
1255
1256 /**
1257 * Get whether parsing of data provided by a proxy is enabled.
1258 *
1259 * @return True if a proxy is providing data to use.
1260 */
1261 public boolean getProxySupport() {
1262 return config.getBoolean(Key.PROXY_SUPPORT);
1263 }
1264
1265 /**
1266 * Get whether to use color codes in Rcon responses.
1267 *
1268 * @return True if color codes will be present in Rcon responses
1269 */
1270 public boolean useRconColors() {
1271 return config.getBoolean(Key.RCON_COLORS);
1272 }
1273
1274 /**
1275 * Gets the {@link MaterialValueManager} for this server.
1276 *
1277 * @return the {@link MaterialValueManager} for this server.
1278 */
1279 public MaterialValueManager getMaterialValueManager() {
1280 return materialValueManager;
1281 }
1282
1283 /**
1284 * Gets the {@link BossBarManager} for this server.
1285 *
1286 * @return the {@link BossBarManager} for this server.
1287 */
1288 public BossBarManager getBossBarManager() {
1289 return bossBarManager;
1290 }
1291
1292 /**
1293 * Get the resource pack url for this server, or {@code null} if not set.
1294 *
1295 * @return The url of the resource pack to use, or {@code null}
1296 */
1297 public String getResourcePackURL() {
1298 return config.getString(Key.RESOURCE_PACK);
1299 }
1300
1301 /**
1302 * Get the resource pack hash for this server, or the empty string if not set.
1303 *
1304 * @return The hash of the resource pack, or the empty string
1305 */
1306 public String getResourcePackHash() {
1307 return config.getString(Key.RESOURCE_PACK_HASH);
1308 }
1309
1310 /**
1311 * Get whether achievements should be announced.
1312 *
1313 * @return True if achievements should be announced in chat.
1314 */
1315 public boolean getAnnounceAchievements() {
1316 return config.getBoolean(Key.ANNOUNCE_ACHIEVEMENTS);
1317 }
1318
1319 ////////////////////////////////////////////////////////////////////////////
1320 // St