PageRenderTime 378ms CodeModel.GetById 38ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/net/glowstone/GlowServer.java

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