001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.bukkit; 020 021import com.google.inject.Guice; 022import com.google.inject.Inject; 023import com.google.inject.Injector; 024import com.google.inject.Key; 025import com.google.inject.Singleton; 026import com.google.inject.Stage; 027import com.google.inject.TypeLiteral; 028import com.plotsquared.bukkit.generator.BukkitPlotGenerator; 029import com.plotsquared.bukkit.inject.BackupModule; 030import com.plotsquared.bukkit.inject.BukkitModule; 031import com.plotsquared.bukkit.inject.PermissionModule; 032import com.plotsquared.bukkit.inject.WorldManagerModule; 033import com.plotsquared.bukkit.listener.BlockEventListener; 034import com.plotsquared.bukkit.listener.BlockEventListener117; 035import com.plotsquared.bukkit.listener.ChunkListener; 036import com.plotsquared.bukkit.listener.EntityEventListener; 037import com.plotsquared.bukkit.listener.EntitySpawnListener; 038import com.plotsquared.bukkit.listener.PaperListener; 039import com.plotsquared.bukkit.listener.PlayerEventListener; 040import com.plotsquared.bukkit.listener.ProjectileEventListener; 041import com.plotsquared.bukkit.listener.ServerListener; 042import com.plotsquared.bukkit.listener.SingleWorldListener; 043import com.plotsquared.bukkit.listener.SpigotListener; 044import com.plotsquared.bukkit.listener.WorldEvents; 045import com.plotsquared.bukkit.placeholder.PAPIPlaceholders; 046import com.plotsquared.bukkit.placeholder.PlaceholderFormatter; 047import com.plotsquared.bukkit.player.BukkitPlayer; 048import com.plotsquared.bukkit.player.BukkitPlayerManager; 049import com.plotsquared.bukkit.util.BukkitUtil; 050import com.plotsquared.bukkit.util.BukkitWorld; 051import com.plotsquared.bukkit.util.SetGenCB; 052import com.plotsquared.bukkit.util.UpdateUtility; 053import com.plotsquared.bukkit.util.task.BukkitTaskManager; 054import com.plotsquared.bukkit.util.task.PaperTimeConverter; 055import com.plotsquared.bukkit.util.task.SpigotTimeConverter; 056import com.plotsquared.bukkit.uuid.EssentialsUUIDService; 057import com.plotsquared.bukkit.uuid.LuckPermsUUIDService; 058import com.plotsquared.bukkit.uuid.OfflinePlayerUUIDService; 059import com.plotsquared.bukkit.uuid.PaperUUIDService; 060import com.plotsquared.bukkit.uuid.SQLiteUUIDService; 061import com.plotsquared.bukkit.uuid.SquirrelIdUUIDService; 062import com.plotsquared.core.PlotPlatform; 063import com.plotsquared.core.PlotSquared; 064import com.plotsquared.core.backup.BackupManager; 065import com.plotsquared.core.components.ComponentPresetManager; 066import com.plotsquared.core.configuration.ConfigurationNode; 067import com.plotsquared.core.configuration.ConfigurationSection; 068import com.plotsquared.core.configuration.ConfigurationUtil; 069import com.plotsquared.core.configuration.Settings; 070import com.plotsquared.core.configuration.Storage; 071import com.plotsquared.core.configuration.caption.ChatFormatter; 072import com.plotsquared.core.configuration.file.YamlConfiguration; 073import com.plotsquared.core.database.DBFunc; 074import com.plotsquared.core.events.RemoveRoadEntityEvent; 075import com.plotsquared.core.events.Result; 076import com.plotsquared.core.generator.GeneratorWrapper; 077import com.plotsquared.core.generator.IndependentPlotGenerator; 078import com.plotsquared.core.generator.SingleWorldGenerator; 079import com.plotsquared.core.inject.annotations.BackgroundPipeline; 080import com.plotsquared.core.inject.annotations.DefaultGenerator; 081import com.plotsquared.core.inject.annotations.ImpromptuPipeline; 082import com.plotsquared.core.inject.annotations.WorldConfig; 083import com.plotsquared.core.inject.annotations.WorldFile; 084import com.plotsquared.core.inject.modules.PlotSquaredModule; 085import com.plotsquared.core.listener.PlotListener; 086import com.plotsquared.core.listener.WESubscriber; 087import com.plotsquared.core.player.PlotPlayer; 088import com.plotsquared.core.plot.Plot; 089import com.plotsquared.core.plot.PlotArea; 090import com.plotsquared.core.plot.PlotAreaTerrainType; 091import com.plotsquared.core.plot.PlotAreaType; 092import com.plotsquared.core.plot.PlotId; 093import com.plotsquared.core.plot.comment.CommentManager; 094import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; 095import com.plotsquared.core.plot.world.PlotAreaManager; 096import com.plotsquared.core.plot.world.SinglePlotArea; 097import com.plotsquared.core.plot.world.SinglePlotAreaManager; 098import com.plotsquared.core.setup.PlotAreaBuilder; 099import com.plotsquared.core.setup.SettingsNodesWrapper; 100import com.plotsquared.core.util.EventDispatcher; 101import com.plotsquared.core.util.FileUtils; 102import com.plotsquared.core.util.PlatformWorldManager; 103import com.plotsquared.core.util.PlayerManager; 104import com.plotsquared.core.util.PremiumVerification; 105import com.plotsquared.core.util.ReflectionUtils; 106import com.plotsquared.core.util.SetupUtils; 107import com.plotsquared.core.util.WorldUtil; 108import com.plotsquared.core.util.task.TaskManager; 109import com.plotsquared.core.util.task.TaskTime; 110import com.plotsquared.core.uuid.CacheUUIDService; 111import com.plotsquared.core.uuid.UUIDPipeline; 112import com.plotsquared.core.uuid.offline.OfflineModeUUIDService; 113import com.sk89q.worldedit.WorldEdit; 114import com.sk89q.worldedit.bukkit.BukkitAdapter; 115import io.papermc.lib.PaperLib; 116import net.kyori.adventure.audience.Audience; 117import net.kyori.adventure.text.Component; 118import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 119import org.apache.logging.log4j.LogManager; 120import org.apache.logging.log4j.Logger; 121import org.bstats.bukkit.Metrics; 122import org.bstats.charts.DrilldownPie; 123import org.bstats.charts.SimplePie; 124import org.bukkit.Bukkit; 125import org.bukkit.Chunk; 126import org.bukkit.Location; 127import org.bukkit.World; 128import org.bukkit.command.PluginCommand; 129import org.bukkit.entity.Entity; 130import org.bukkit.entity.LivingEntity; 131import org.bukkit.entity.Player; 132import org.bukkit.event.Listener; 133import org.bukkit.generator.ChunkGenerator; 134import org.bukkit.metadata.FixedMetadataValue; 135import org.bukkit.metadata.MetadataValue; 136import org.bukkit.plugin.Plugin; 137import org.bukkit.plugin.java.JavaPlugin; 138import org.checkerframework.checker.nullness.qual.NonNull; 139import org.checkerframework.checker.nullness.qual.Nullable; 140import org.incendo.serverlib.ServerLib; 141 142import java.io.File; 143import java.lang.reflect.Method; 144import java.util.ArrayList; 145import java.util.Arrays; 146import java.util.Collections; 147import java.util.Comparator; 148import java.util.HashMap; 149import java.util.HashSet; 150import java.util.Iterator; 151import java.util.List; 152import java.util.Locale; 153import java.util.Map; 154import java.util.Queue; 155import java.util.Set; 156import java.util.UUID; 157import java.util.concurrent.ExecutionException; 158import java.util.concurrent.Executors; 159import java.util.concurrent.LinkedBlockingQueue; 160import java.util.concurrent.TimeUnit; 161 162import static com.plotsquared.core.util.PremiumVerification.getDownloadID; 163import static com.plotsquared.core.util.PremiumVerification.getResourceID; 164import static com.plotsquared.core.util.PremiumVerification.getUserID; 165import static com.plotsquared.core.util.ReflectionUtils.getRefClass; 166 167@SuppressWarnings("unused") 168@Singleton 169public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPlatform<Player> { 170 171 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BukkitPlatform.class.getSimpleName()); 172 private static final int BSTATS_ID = 1404; 173 174 static { 175 try { 176 Settings.load(new File(PlotSquared.platform().getDirectory(), "settings.yml")); 177 } catch (Throwable ignored) { 178 } 179 } 180 181 private int[] version; 182 private String pluginName; 183 private SingleWorldListener singleWorldListener; 184 private Method methodUnloadChunk0; 185 private boolean methodUnloadSetup = false; 186 private boolean metricsStarted; 187 private boolean faweHook = false; 188 189 private Injector injector; 190 191 @Inject 192 private PlotAreaManager plotAreaManager; 193 @Inject 194 private EventDispatcher eventDispatcher; 195 @Inject 196 private PlotListener plotListener; 197 @Inject 198 @WorldConfig 199 private YamlConfiguration worldConfiguration; 200 @Inject 201 @WorldFile 202 private File worldfile; 203 @Inject 204 private BukkitPlayerManager playerManager; 205 @Inject 206 private BackupManager backupManager; 207 @Inject 208 @ImpromptuPipeline 209 private UUIDPipeline impromptuPipeline; 210 @Inject 211 @BackgroundPipeline 212 private UUIDPipeline backgroundPipeline; 213 @Inject 214 private PlatformWorldManager<World> worldManager; 215 private Locale serverLocale; 216 217 @SuppressWarnings("StringSplitter") 218 @Override 219 public int @NonNull [] serverVersion() { 220 if (this.version == null) { 221 try { 222 this.version = new int[3]; 223 String[] split = Bukkit.getBukkitVersion().split("-")[0].split("\\."); 224 this.version[0] = Integer.parseInt(split[0]); 225 this.version[1] = Integer.parseInt(split[1]); 226 if (split.length == 3) { 227 this.version[2] = Integer.parseInt(split[2]); 228 } 229 } catch (NumberFormatException e) { 230 e.printStackTrace(); 231 return new int[]{1, 13, 0}; 232 } 233 } 234 return this.version; 235 } 236 237 @Override 238 public int versionMinHeight() { 239 return serverVersion()[1] >= 18 ? -64 : 0; 240 } 241 242 @Override 243 public int versionMaxHeight() { 244 return serverVersion()[1] >= 18 ? 319 : 255; 245 } 246 247 @Override 248 public @NonNull String serverImplementation() { 249 return Bukkit.getVersion(); 250 } 251 252 @Override 253 public void onEnable() { 254 this.pluginName = getDescription().getName(); 255 256 final TaskTime.TimeConverter timeConverter; 257 if (PaperLib.isPaper()) { 258 timeConverter = new PaperTimeConverter(); 259 } else { 260 timeConverter = new SpigotTimeConverter(); 261 } 262 263 // Stuff that needs to be created before the PlotSquared instance 264 PlotPlayer.registerConverter(Player.class, BukkitUtil::adapt); 265 TaskManager.setPlatformImplementation(new BukkitTaskManager(this, timeConverter)); 266 267 final PlotSquared plotSquared = new PlotSquared(this, "Bukkit"); 268 269 // FastAsyncWorldEdit 270 if (Settings.FAWE_Components.FAWE_HOOK) { 271 Plugin fawe = getServer().getPluginManager().getPlugin("FastAsyncWorldEdit"); 272 if (fawe != null) { 273 try { 274 Class.forName("com.fastasyncworldedit.bukkit.regions.plotsquared.FaweQueueCoordinator"); 275 faweHook = true; 276 } catch (Exception ignored) { 277 LOGGER.error("Incompatible version of FastAsyncWorldEdit to enable hook, please upgrade: https://ci.athion" + 278 ".net/job/FastAsyncWorldEdit/"); 279 } 280 } 281 } 282 283 // We create the injector after PlotSquared has been initialized, so that we have access 284 // to generated instances and settings 285 this.injector = Guice 286 .createInjector( 287 Stage.PRODUCTION, 288 new PermissionModule(), 289 new WorldManagerModule(), 290 new PlotSquaredModule(), 291 new BukkitModule(this), 292 new BackupModule() 293 ); 294 this.injector.injectMembers(this); 295 296 this.serverLocale = Locale.forLanguageTag(Settings.Enabled_Components.DEFAULT_LOCALE); 297 298 if (PremiumVerification.isPremium() && Settings.Enabled_Components.UPDATE_NOTIFICATIONS) { 299 injector.getInstance(UpdateUtility.class).updateChecker(); 300 } 301 302 if (PremiumVerification.isPremium()) { 303 LOGGER.info("PlotSquared version licensed to Spigot user {}", getUserID()); 304 LOGGER.info("https://www.spigotmc.org/resources/{}", getResourceID()); 305 LOGGER.info("Download ID: {}", getDownloadID()); 306 LOGGER.info("Thanks for supporting us :)"); 307 } else { 308 LOGGER.info("Couldn't verify purchase :("); 309 } 310 311 // Database 312 if (Settings.Enabled_Components.DATABASE) { 313 plotSquared.setupDatabase(); 314 } 315 316 // Check if we need to convert old flag values, etc 317 if (!plotSquared.getConfigurationVersion().equalsIgnoreCase("v5")) { 318 // Perform upgrade 319 if (DBFunc.dbManager.convertFlags()) { 320 LOGGER.info("Flags were converted successfully!"); 321 // Update the config version 322 try { 323 plotSquared.setConfigurationVersion("v5"); 324 } catch (final Exception e) { 325 e.printStackTrace(); 326 } 327 } 328 } 329 330 // Comments 331 CommentManager.registerDefaultInboxes(); 332 333 // Do stuff that was previously done in PlotSquared 334 // Kill entities 335 if (Settings.Enabled_Components.KILL_ROAD_MOBS || Settings.Enabled_Components.KILL_ROAD_VEHICLES) { 336 this.runEntityTask(); 337 } 338 339 // WorldEdit 340 if (Settings.Enabled_Components.WORLDEDIT_RESTRICTIONS) { 341 try { 342 WorldEdit.getInstance().getEventBus().register(this.injector().getInstance(WESubscriber.class)); 343 LOGGER.info("{} hooked into WorldEdit", this.pluginName()); 344 } catch (Throwable e) { 345 LOGGER.error( 346 "Incompatible version of WorldEdit, please upgrade: https://builds.enginehub.org/job/worldedit?branch=master"); 347 } 348 } 349 350 if (Settings.Enabled_Components.EVENTS) { 351 getServer().getPluginManager().registerEvents(injector().getInstance(PlayerEventListener.class), this); 352 getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener.class), this); 353 if (serverVersion()[1] >= 17) { 354 getServer().getPluginManager().registerEvents(injector().getInstance(BlockEventListener117.class), this); 355 } 356 getServer().getPluginManager().registerEvents(injector().getInstance(EntityEventListener.class), this); 357 getServer().getPluginManager().registerEvents(injector().getInstance(ProjectileEventListener.class), this); 358 getServer().getPluginManager().registerEvents(injector().getInstance(ServerListener.class), this); 359 getServer().getPluginManager().registerEvents(injector().getInstance(EntitySpawnListener.class), this); 360 if (PaperLib.isPaper() && Settings.Paper_Components.PAPER_LISTENERS) { 361 getServer().getPluginManager().registerEvents(injector().getInstance(PaperListener.class), this); 362 } else { 363 getServer().getPluginManager().registerEvents(injector().getInstance(SpigotListener.class), this); 364 } 365 this.plotListener.startRunnable(); 366 } 367 368 // Required 369 getServer().getPluginManager().registerEvents(injector().getInstance(WorldEvents.class), this); 370 if (Settings.Enabled_Components.CHUNK_PROCESSOR) { 371 getServer().getPluginManager().registerEvents(injector().getInstance(ChunkListener.class), this); 372 } 373 374 // Commands 375 if (Settings.Enabled_Components.COMMANDS) { 376 this.registerCommands(); 377 } 378 379 // Permissions 380 this.permissionHandler().initialize(); 381 382 if (Settings.Enabled_Components.COMPONENT_PRESETS) { 383 try { 384 injector().getInstance(ComponentPresetManager.class); 385 } catch (final Exception e) { 386 LOGGER.error("Failed to initialize the preset system", e); 387 } 388 } 389 390 // World generators: 391 final ConfigurationSection section = this.worldConfiguration.getConfigurationSection("worlds"); 392 final WorldUtil worldUtil = injector().getInstance(WorldUtil.class); 393 394 if (section != null) { 395 for (String world : section.getKeys(false)) { 396 if (world.equals("CheckingPlotSquaredGenerator")) { 397 continue; 398 } 399 if (worldUtil.isWorld(world)) { 400 this.setGenerator(world); 401 } 402 } 403 TaskManager.runTaskLater(() -> { 404 for (String world : section.getKeys(false)) { 405 if (world.equals("CheckingPlotSquaredGenerator")) { 406 continue; 407 } 408 if (!worldUtil.isWorld(world) && !world.equals("*")) { 409 if (Settings.DEBUG) { 410 LOGGER.warn( 411 "`{}` was not properly loaded - {} will now try to load it properly", 412 world, 413 this.pluginName() 414 ); 415 LOGGER.warn( 416 "- Are you trying to delete this world? Remember to remove it from the worlds.yml, bukkit.yml and multiverse worlds.yml"); 417 LOGGER.warn("- Your world management plugin may be faulty (or non existent)"); 418 LOGGER.warn("- The named world is not a plot world"); 419 LOGGER.warn("This message may also be a false positive and could be ignored."); 420 } 421 this.setGenerator(world); 422 } 423 } 424 }, TaskTime.ticks(1L)); 425 } 426 427 plotSquared.startExpiryTasks(); 428 429 // Once the server has loaded force updating all generators known to PlotSquared 430 TaskManager.runTaskLater(() -> PlotSquared.platform().setupUtils().updateGenerators(true), TaskTime.ticks(1L)); 431 432 // Services are accessed in order 433 final CacheUUIDService cacheUUIDService = new CacheUUIDService(Settings.UUID.UUID_CACHE_SIZE); 434 this.impromptuPipeline.registerService(cacheUUIDService); 435 this.backgroundPipeline.registerService(cacheUUIDService); 436 this.impromptuPipeline.registerConsumer(cacheUUIDService); 437 this.backgroundPipeline.registerConsumer(cacheUUIDService); 438 439 // Now, if the server is in offline mode we can only use profiles and direct UUID 440 // access, and so we skip the player profile stuff as well as SquirrelID (Mojang lookups) 441 if (Settings.UUID.OFFLINE) { 442 final OfflineModeUUIDService offlineModeUUIDService = new OfflineModeUUIDService(); 443 this.impromptuPipeline.registerService(offlineModeUUIDService); 444 this.backgroundPipeline.registerService(offlineModeUUIDService); 445 LOGGER.info("(UUID) Using the offline mode UUID service"); 446 } 447 448 if (Settings.UUID.SERVICE_BUKKIT) { 449 final OfflinePlayerUUIDService offlinePlayerUUIDService = new OfflinePlayerUUIDService(); 450 this.impromptuPipeline.registerService(offlinePlayerUUIDService); 451 this.backgroundPipeline.registerService(offlinePlayerUUIDService); 452 } 453 454 final SQLiteUUIDService sqLiteUUIDService = new SQLiteUUIDService("user_cache.db"); 455 456 final SQLiteUUIDService legacyUUIDService; 457 if (Settings.UUID.LEGACY_DATABASE_SUPPORT && FileUtils 458 .getFile(PlotSquared.platform().getDirectory(), "usercache.db") 459 .exists()) { 460 legacyUUIDService = new SQLiteUUIDService("usercache.db"); 461 } else { 462 legacyUUIDService = null; 463 } 464 465 final LuckPermsUUIDService luckPermsUUIDService; 466 if (Settings.UUID.SERVICE_LUCKPERMS && Bukkit.getPluginManager().getPlugin("LuckPerms") != null) { 467 luckPermsUUIDService = new LuckPermsUUIDService(); 468 LOGGER.info("(UUID) Using LuckPerms as a complementary UUID service"); 469 } else { 470 luckPermsUUIDService = null; 471 } 472 473 final EssentialsUUIDService essentialsUUIDService; 474 if (Settings.UUID.SERVICE_ESSENTIALSX && Bukkit.getPluginManager().getPlugin("Essentials") != null) { 475 essentialsUUIDService = new EssentialsUUIDService(); 476 LOGGER.info("(UUID) Using EssentialsX as a complementary UUID service"); 477 } else { 478 essentialsUUIDService = null; 479 } 480 481 if (!Settings.UUID.OFFLINE) { 482 // If running Paper we'll also try to use their profiles 483 if (Bukkit.getOnlineMode() && PaperLib.isPaper() && Settings.UUID.SERVICE_PAPER) { 484 final PaperUUIDService paperUUIDService = new PaperUUIDService(); 485 this.impromptuPipeline.registerService(paperUUIDService); 486 this.backgroundPipeline.registerService(paperUUIDService); 487 LOGGER.info("(UUID) Using Paper as a complementary UUID service"); 488 } 489 490 this.impromptuPipeline.registerService(sqLiteUUIDService); 491 this.backgroundPipeline.registerService(sqLiteUUIDService); 492 this.impromptuPipeline.registerConsumer(sqLiteUUIDService); 493 this.backgroundPipeline.registerConsumer(sqLiteUUIDService); 494 495 if (legacyUUIDService != null) { 496 this.impromptuPipeline.registerService(legacyUUIDService); 497 this.backgroundPipeline.registerService(legacyUUIDService); 498 } 499 500 // Plugin providers 501 if (luckPermsUUIDService != null) { 502 this.impromptuPipeline.registerService(luckPermsUUIDService); 503 this.backgroundPipeline.registerService(luckPermsUUIDService); 504 } 505 if (essentialsUUIDService != null) { 506 this.impromptuPipeline.registerService(essentialsUUIDService); 507 this.backgroundPipeline.registerService(essentialsUUIDService); 508 } 509 510 if (Settings.UUID.IMPROMPTU_SERVICE_MOJANG_API) { 511 final SquirrelIdUUIDService impromptuMojangService = new SquirrelIdUUIDService(Settings.UUID.IMPROMPTU_LIMIT); 512 this.impromptuPipeline.registerService(impromptuMojangService); 513 } 514 final SquirrelIdUUIDService backgroundMojangService = new SquirrelIdUUIDService(Settings.UUID.BACKGROUND_LIMIT); 515 this.backgroundPipeline.registerService(backgroundMojangService); 516 } else { 517 this.impromptuPipeline.registerService(sqLiteUUIDService); 518 this.backgroundPipeline.registerService(sqLiteUUIDService); 519 this.impromptuPipeline.registerConsumer(sqLiteUUIDService); 520 this.backgroundPipeline.registerConsumer(sqLiteUUIDService); 521 522 if (legacyUUIDService != null) { 523 this.impromptuPipeline.registerService(legacyUUIDService); 524 this.backgroundPipeline.registerService(legacyUUIDService); 525 } 526 } 527 528 this.impromptuPipeline.storeImmediately("*", DBFunc.EVERYONE); 529 530 if (Settings.UUID.BACKGROUND_CACHING_ENABLED) { 531 this.startUuidCaching(sqLiteUUIDService, cacheUUIDService); 532 } 533 534 if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { 535 injector.getInstance(PAPIPlaceholders.class).register(); 536 if (Settings.Enabled_Components.EXTERNAL_PLACEHOLDERS) { 537 ChatFormatter.formatters.add(injector().getInstance(PlaceholderFormatter.class)); 538 } 539 LOGGER.info("PlotSquared hooked into PlaceholderAPI"); 540 } 541 542 this.startMetrics(); 543 544 if (Settings.Enabled_Components.WORLDS) { 545 TaskManager.getPlatformImplementation().taskRepeat(this::unload, TaskTime.seconds(1L)); 546 try { 547 singleWorldListener = injector().getInstance(SingleWorldListener.class); 548 Bukkit.getPluginManager().registerEvents(singleWorldListener, this); 549 } catch (Exception e) { 550 e.printStackTrace(); 551 } 552 } 553 554 // Clean up potential memory leak 555 Bukkit.getScheduler().runTaskTimer(this, () -> { 556 try { 557 for (final PlotPlayer<? extends Player> player : this.playerManager().getPlayers()) { 558 if (player.getPlatformPlayer() == null || !player.getPlatformPlayer().isOnline()) { 559 this.playerManager().removePlayer(player); 560 } 561 } 562 } catch (final Exception e) { 563 getLogger().warning("Failed to clean up players: " + e.getMessage()); 564 } 565 }, 100L, 100L); 566 567 // Check if we are in a safe environment 568 ServerLib.checkUnsafeForks(); 569 } 570 571 private void unload() { 572 if (!this.methodUnloadSetup) { 573 this.methodUnloadSetup = true; 574 try { 575 ReflectionUtils.RefClass classCraftWorld = getRefClass("{cb}.CraftWorld"); 576 this.methodUnloadChunk0 = classCraftWorld.getRealClass().getDeclaredMethod( 577 "unloadChunk0", 578 int.class, 579 int.class, 580 boolean.class 581 ); 582 this.methodUnloadChunk0.setAccessible(true); 583 } catch (Throwable event) { 584 event.printStackTrace(); 585 } 586 } 587 588 if (this.plotAreaManager instanceof SinglePlotAreaManager) { 589 long start = System.currentTimeMillis(); 590 final SinglePlotArea area = ((SinglePlotAreaManager) this.plotAreaManager).getArea(); 591 592 outer: 593 for (final World world : Bukkit.getWorlds()) { 594 final String name = world.getName(); 595 final char char0 = name.charAt(0); 596 if (!Character.isDigit(char0) && char0 != '-') { 597 continue; 598 } 599 600 if (!world.getPlayers().isEmpty()) { 601 continue; 602 } 603 604 PlotId id; 605 try { 606 id = PlotId.fromString(name); 607 } catch (IllegalArgumentException ignored) { 608 continue; 609 } 610 final Plot plot = area.getOwnedPlot(id); 611 if (plot != null) { 612 if (!plot.getFlag(ServerPlotFlag.class) || PlotSquared 613 .platform() 614 .playerManager() 615 .getPlayerIfExists(plot.getOwner()) == null) { 616 if (world.getKeepSpawnInMemory()) { 617 world.setKeepSpawnInMemory(false); 618 return; 619 } 620 final Chunk[] chunks = world.getLoadedChunks(); 621 if (chunks.length == 0) { 622 if (!Bukkit.unloadWorld(world, true)) { 623 LOGGER.warn("Failed to unload {}", world.getName()); 624 } 625 return; 626 } else { 627 int index = 0; 628 do { 629 final Chunk chunkI = chunks[index++]; 630 boolean result; 631 if (methodUnloadChunk0 != null) { 632 try { 633 result = (boolean) methodUnloadChunk0.invoke(world, chunkI.getX(), chunkI.getZ(), true); 634 } catch (Throwable e) { 635 methodUnloadChunk0 = null; 636 e.printStackTrace(); 637 continue outer; 638 } 639 } else { 640 result = world.unloadChunk(chunkI.getX(), chunkI.getZ(), true); 641 } 642 if (!result) { 643 continue outer; 644 } 645 if (System.currentTimeMillis() - start > 5) { 646 return; 647 } 648 } while (index < chunks.length); 649 } 650 } 651 } 652 } 653 } 654 } 655 656 private void startUuidCaching( 657 final @NonNull SQLiteUUIDService sqLiteUUIDService, 658 final @NonNull CacheUUIDService cacheUUIDService 659 ) { 660 // Record all unique UUID's and put them into a queue 661 final Set<UUID> uuidSet = new HashSet<>(); 662 PlotSquared.get().forEachPlotRaw(plot -> { 663 uuidSet.add(plot.getOwnerAbs()); 664 uuidSet.addAll(plot.getMembers()); 665 uuidSet.addAll(plot.getTrusted()); 666 uuidSet.addAll(plot.getDenied()); 667 }); 668 final Queue<UUID> uuidQueue = new LinkedBlockingQueue<>(uuidSet); 669 670 LOGGER.info("(UUID) {} UUIDs will be cached", uuidQueue.size()); 671 672 Executors.newSingleThreadScheduledExecutor().schedule(() -> { 673 // Begin by reading all the SQLite cache at once 674 cacheUUIDService.accept(sqLiteUUIDService.getAll()); 675 // Now fetch names for all known UUIDs 676 final int totalSize = uuidQueue.size(); 677 int read = 0; 678 LOGGER.info("(UUID) PlotSquared will fetch UUIDs in groups of {}", Settings.UUID.BACKGROUND_LIMIT); 679 final List<UUID> uuidList = new ArrayList<>(Settings.UUID.BACKGROUND_LIMIT); 680 681 // Used to indicate that the second retrieval has been attempted 682 boolean secondRun = false; 683 684 while (!uuidQueue.isEmpty() || !uuidList.isEmpty()) { 685 if (!uuidList.isEmpty() && secondRun) { 686 LOGGER.warn("(UUID) Giving up on last batch. Fetching new batch instead"); 687 uuidList.clear(); 688 } 689 if (uuidList.isEmpty()) { 690 // Retrieve the secondRun variable to indicate that we're retrieving a 691 // fresh batch 692 secondRun = false; 693 // Populate the request list 694 for (int i = 0; i < Settings.UUID.BACKGROUND_LIMIT && !uuidQueue.isEmpty(); i++) { 695 uuidList.add(uuidQueue.poll()); 696 read++; 697 } 698 } else { 699 // If the list isn't empty then this is a second run for 700 // an old batch, so we re-use the patch 701 secondRun = true; 702 } 703 try { 704 PlotSquared.get().getBackgroundUUIDPipeline().getNames(uuidList).get(); 705 // Clear the list if we successfully index all the names 706 uuidList.clear(); 707 // Print progress 708 final double percentage = ((double) read / (double) totalSize) * 100.0D; 709 if (Settings.DEBUG) { 710 LOGGER.info("(UUID) PlotSquared has cached {} of UUIDs", String.format("%.1f%%", percentage)); 711 } 712 } catch (final InterruptedException | ExecutionException e) { 713 LOGGER.error("(UUID) Failed to retrieve last batch. Will try again", e); 714 } 715 } 716 LOGGER.info("(UUID) PlotSquared has cached all UUIDs"); 717 }, 10, TimeUnit.SECONDS); 718 } 719 720 @Override 721 public void onDisable() { 722 PlotSquared.get().disable(); 723 Bukkit.getScheduler().cancelTasks(this); 724 } 725 726 @Override 727 public void shutdown() { 728 this.getServer().getPluginManager().disablePlugin(this); 729 } 730 731 @Override 732 public void shutdownServer() { 733 getServer().shutdown(); 734 } 735 736 private void registerCommands() { 737 final BukkitCommand bukkitCommand = new BukkitCommand(); 738 final PluginCommand plotCommand = getCommand("plots"); 739 if (plotCommand != null) { 740 plotCommand.setExecutor(bukkitCommand); 741 plotCommand.setAliases(Arrays.asList("p", "ps", "plotme", "plot")); 742 plotCommand.setTabCompleter(bukkitCommand); 743 } 744 } 745 746 @Override 747 public @NonNull File getDirectory() { 748 return getDataFolder(); 749 } 750 751 @Override 752 public @NonNull File worldContainer() { 753 return Bukkit.getWorldContainer(); 754 } 755 756 @SuppressWarnings("deprecation") 757 private void runEntityTask() { 758 TaskManager.runTaskRepeat(() -> this.plotAreaManager.forEachPlotArea(plotArea -> { 759 final World world = Bukkit.getWorld(plotArea.getWorldName()); 760 try { 761 if (world == null) { 762 return; 763 } 764 List<Entity> entities = world.getEntities(); 765 Iterator<Entity> iterator = entities.iterator(); 766 while (iterator.hasNext()) { 767 Entity entity = iterator.next(); 768 switch (entity.getType().toString()) { 769 case "EGG": 770 case "FISHING_HOOK": 771 case "ENDER_SIGNAL": 772 case "AREA_EFFECT_CLOUD": 773 case "EXPERIENCE_ORB": 774 case "LEASH_HITCH": 775 case "FIREWORK": 776 case "LIGHTNING": 777 case "WITHER_SKULL": 778 case "UNKNOWN": 779 case "PLAYER": 780 // non moving / unmovable 781 continue; 782 case "THROWN_EXP_BOTTLE": 783 case "SPLASH_POTION": 784 case "SNOWBALL": 785 case "SHULKER_BULLET": 786 case "SPECTRAL_ARROW": 787 case "ENDER_PEARL": 788 case "ARROW": 789 case "LLAMA_SPIT": 790 case "TRIDENT": 791 // managed elsewhere | projectile 792 continue; 793 case "ITEM_FRAME": 794 case "PAINTING": 795 // Not vehicles 796 continue; 797 case "ARMOR_STAND": 798 // Temporarily classify as vehicle 799 case "MINECART": 800 case "MINECART_CHEST": 801 case "MINECART_COMMAND": 802 case "MINECART_FURNACE": 803 case "MINECART_HOPPER": 804 case "MINECART_MOB_SPAWNER": 805 case "ENDER_CRYSTAL": 806 case "MINECART_TNT": 807 case "BOAT": 808 if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) { 809 com.plotsquared.core.location.Location location = BukkitUtil.adapt(entity.getLocation()); 810 Plot plot = location.getPlot(); 811 if (plot == null) { 812 if (location.isPlotArea()) { 813 if (entity.hasMetadata("ps-tmp-teleport")) { 814 continue; 815 } 816 this.removeRoadEntity(entity, iterator); 817 } 818 continue; 819 } 820 List<MetadataValue> meta = entity.getMetadata("plot"); 821 if (meta.isEmpty()) { 822 continue; 823 } 824 Plot origin = (Plot) meta.get(0).value(); 825 if (!plot.equals(origin.getBasePlot(false))) { 826 if (entity.hasMetadata("ps-tmp-teleport")) { 827 continue; 828 } 829 this.removeRoadEntity(entity, iterator); 830 } 831 } 832 continue; 833 case "SMALL_FIREBALL": 834 case "FIREBALL": 835 case "DRAGON_FIREBALL": 836 case "DROPPED_ITEM": 837 if (Settings.Enabled_Components.KILL_ROAD_ITEMS 838 && plotArea.getOwnedPlotAbs(BukkitUtil.adapt(entity.getLocation())) == null) { 839 this.removeRoadEntity(entity, iterator); 840 } 841 // dropped item 842 continue; 843 case "PRIMED_TNT": 844 case "FALLING_BLOCK": 845 // managed elsewhere 846 continue; 847 case "SHULKER": 848 if (Settings.Enabled_Components.KILL_ROAD_MOBS && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS || entity.getCustomName() == null)) { 849 LivingEntity livingEntity = (LivingEntity) entity; 850 List<MetadataValue> meta = entity.getMetadata("shulkerPlot"); 851 if (!meta.isEmpty()) { 852 if (livingEntity.isLeashed() && !Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS) { 853 continue; 854 } 855 List<MetadataValue> keep = entity.getMetadata("keep"); 856 if (!keep.isEmpty()) { 857 continue; 858 } 859 860 PlotId originalPlotId = (PlotId) meta.get(0).value(); 861 if (originalPlotId != null) { 862 com.plotsquared.core.location.Location pLoc = BukkitUtil.adapt(entity.getLocation()); 863 PlotArea area = pLoc.getPlotArea(); 864 if (area != null) { 865 Plot currentPlot = area.getPlotAbs(pLoc); 866 if (currentPlot == null || !originalPlotId.equals(currentPlot.getId())) { 867 if (entity.hasMetadata("ps-tmp-teleport")) { 868 continue; 869 } 870 this.removeRoadEntity(entity, iterator); 871 } 872 } 873 } 874 } else { 875 //This is to apply the metadata to already spawned shulkers (see EntitySpawnListener.java) 876 com.plotsquared.core.location.Location pLoc = BukkitUtil.adapt(entity.getLocation()); 877 PlotArea area = pLoc.getPlotArea(); 878 if (area != null) { 879 Plot currentPlot = area.getPlotAbs(pLoc); 880 if (currentPlot != null) { 881 entity.setMetadata( 882 "shulkerPlot", 883 new FixedMetadataValue((Plugin) PlotSquared.platform(), currentPlot.getId()) 884 ); 885 } 886 } 887 } 888 } 889 continue; 890 case "ZOMBIFIED_PIGLIN": 891 case "PIGLIN_BRUTE": 892 case "LLAMA": 893 case "DONKEY": 894 case "MULE": 895 case "ZOMBIE_HORSE": 896 case "SKELETON_HORSE": 897 case "HUSK": 898 case "ELDER_GUARDIAN": 899 case "WITHER_SKELETON": 900 case "STRAY": 901 case "ZOMBIE_VILLAGER": 902 case "EVOKER": 903 case "EVOKER_FANGS": 904 case "VEX": 905 case "VINDICATOR": 906 case "POLAR_BEAR": 907 case "BAT": 908 case "BLAZE": 909 case "CAVE_SPIDER": 910 case "CHICKEN": 911 case "COW": 912 case "CREEPER": 913 case "ENDERMAN": 914 case "ENDERMITE": 915 case "ENDER_DRAGON": 916 case "GHAST": 917 case "GIANT": 918 case "GUARDIAN": 919 case "HORSE": 920 case "IRON_GOLEM": 921 case "MAGMA_CUBE": 922 case "MUSHROOM_COW": 923 case "OCELOT": 924 case "PIG": 925 case "PIG_ZOMBIE": 926 case "RABBIT": 927 case "SHEEP": 928 case "SILVERFISH": 929 case "SKELETON": 930 case "SLIME": 931 case "SNOWMAN": 932 case "SPIDER": 933 case "SQUID": 934 case "VILLAGER": 935 case "WITCH": 936 case "WITHER": 937 case "WOLF": 938 case "ZOMBIE": 939 case "PARROT": 940 case "SALMON": 941 case "DOLPHIN": 942 case "TROPICAL_FISH": 943 case "DROWNED": 944 case "COD": 945 case "TURTLE": 946 case "PUFFERFISH": 947 case "PHANTOM": 948 case "ILLUSIONER": 949 case "CAT": 950 case "PANDA": 951 case "FOX": 952 case "PILLAGER": 953 case "TRADER_LLAMA": 954 case "WANDERING_TRADER": 955 case "RAVAGER": 956 case "BEE": 957 case "HOGLIN": 958 case "PIGLIN": 959 case "ZOGLIN": 960 default: { 961 if (Settings.Enabled_Components.KILL_ROAD_MOBS) { 962 Location location = entity.getLocation(); 963 if (BukkitUtil.adapt(location).isPlotRoad()) { 964 if (entity instanceof LivingEntity livingEntity) { 965 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS || !livingEntity.isLeashed()) 966 || !entity.hasMetadata("keep")) { 967 Entity passenger = entity.getPassenger(); 968 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS 969 || !((passenger instanceof Player) || livingEntity.isLeashed())) 970 && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS || entity.getCustomName() == null) 971 && entity.getMetadata("keep").isEmpty()) { 972 if (entity.hasMetadata("ps-tmp-teleport")) { 973 continue; 974 } 975 this.removeRoadEntity(entity, iterator); 976 } 977 } 978 } else { 979 Entity passenger = entity.getPassenger(); 980 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS || !(passenger instanceof Player)) 981 && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS && entity.getCustomName() != null) 982 && entity.getMetadata("keep").isEmpty()) { 983 if (entity.hasMetadata("ps-tmp-teleport")) { 984 continue; 985 } 986 this.removeRoadEntity(entity, iterator); 987 } 988 } 989 } 990 } 991 } 992 } 993 } 994 } catch (Throwable e) { 995 e.printStackTrace(); 996 } 997 }), TaskTime.seconds(1L)); 998 } 999 1000 private void removeRoadEntity(Entity entity, Iterator<Entity> entityIterator) { 1001 RemoveRoadEntityEvent event = eventDispatcher.callRemoveRoadEntity(BukkitAdapter.adapt(entity)); 1002 1003 if (event.getEventResult() == Result.DENY) { 1004 return; 1005 } 1006 1007 entityIterator.remove(); 1008 entity.remove(); 1009 } 1010 1011 @Override 1012 public @Nullable 1013 final ChunkGenerator getDefaultWorldGenerator( 1014 final @NonNull String worldName, 1015 final @Nullable String id 1016 ) { 1017 final IndependentPlotGenerator result; 1018 if (id != null && id.equalsIgnoreCase("single")) { 1019 result = injector().getInstance(SingleWorldGenerator.class); 1020 } else { 1021 result = injector().getInstance(Key.get(IndependentPlotGenerator.class, DefaultGenerator.class)); 1022 if (!PlotSquared.get().setupPlotWorld(worldName, id, result)) { 1023 return null; 1024 } 1025 } 1026 return (ChunkGenerator) result.specify(worldName); 1027 } 1028 1029 @Override 1030 public @Nullable GeneratorWrapper<?> getGenerator( 1031 final @NonNull String world, 1032 final @Nullable String name 1033 ) { 1034 if (name == null) { 1035 return null; 1036 } 1037 final Plugin genPlugin = Bukkit.getPluginManager().getPlugin(name); 1038 if (genPlugin != null && genPlugin.isEnabled()) { 1039 ChunkGenerator gen = genPlugin.getDefaultWorldGenerator(world, ""); 1040 if (gen instanceof GeneratorWrapper<?>) { 1041 return (GeneratorWrapper<?>) gen; 1042 } 1043 return new BukkitPlotGenerator(world, gen, this.plotAreaManager); 1044 } else { 1045 return new BukkitPlotGenerator( 1046 world, 1047 injector().getInstance(Key.get(IndependentPlotGenerator.class, DefaultGenerator.class)), 1048 this.plotAreaManager 1049 ); 1050 } 1051 } 1052 1053 @Override 1054 public void startMetrics() { 1055 if (this.metricsStarted) { 1056 return; 1057 } 1058 this.metricsStarted = true; 1059 Metrics metrics = new Metrics(this, BSTATS_ID); // bstats 1060 metrics.addCustomChart(new DrilldownPie("area_types", () -> { 1061 final Map<String, Map<String, Integer>> map = new HashMap<>(); 1062 for (final PlotAreaType plotAreaType : PlotAreaType.values()) { 1063 final Map<String, Integer> terrainTypes = new HashMap<>(); 1064 for (final PlotAreaTerrainType plotAreaTerrainType : PlotAreaTerrainType.values()) { 1065 terrainTypes.put(plotAreaTerrainType.name().toLowerCase(), 0); 1066 } 1067 map.put(plotAreaType.name().toLowerCase(), terrainTypes); 1068 } 1069 for (final PlotArea plotArea : this.plotAreaManager.getAllPlotAreas()) { 1070 final Map<String, Integer> terrainTypeMap = map.get(plotArea.getType().name().toLowerCase()); 1071 terrainTypeMap.put( 1072 plotArea.getTerrain().name().toLowerCase(), 1073 terrainTypeMap.get(plotArea.getTerrain().name().toLowerCase()) + 1 1074 ); 1075 } 1076 return map; 1077 })); 1078 metrics.addCustomChart(new SimplePie( 1079 "premium", 1080 () -> PremiumVerification.isPremium() ? "Premium" : "Non-Premium" 1081 )); 1082 metrics.addCustomChart(new SimplePie("worlds", () -> Settings.Enabled_Components.WORLDS ? "true" : "false")); 1083 metrics.addCustomChart(new SimplePie("economy", () -> Settings.Enabled_Components.ECONOMY ? "true" : "false")); 1084 metrics.addCustomChart(new SimplePie( 1085 "plot_expiry", 1086 () -> Settings.Enabled_Components.PLOT_EXPIRY ? "true" : "false" 1087 )); 1088 metrics.addCustomChart(new SimplePie("database_type", () -> Storage.MySQL.USE ? "MySQL" : "SQLite")); 1089 metrics.addCustomChart(new SimplePie( 1090 "worldedit_implementation", 1091 () -> Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null ? "FastAsyncWorldEdit" : "WorldEdit" 1092 )); 1093 metrics.addCustomChart(new SimplePie("offline_mode", () -> Settings.UUID.OFFLINE ? "true" : "false")); 1094 metrics.addCustomChart(new SimplePie("offline_mode_force", () -> Settings.UUID.FORCE_LOWERCASE ? "true" : "false")); 1095 } 1096 1097 @Override 1098 public void unregister(final @NonNull PlotPlayer<?> player) { 1099 PlotSquared.platform().playerManager().removePlayer(player.getUUID()); 1100 } 1101 1102 @Override 1103 public void setGenerator(final @NonNull String worldName) { 1104 World world = BukkitUtil.getWorld(worldName); 1105 if (world == null) { 1106 // create world 1107 ConfigurationSection worldConfig = this.worldConfiguration.getConfigurationSection("worlds." + worldName); 1108 String manager = worldConfig.getString("generator.plugin", pluginName()); 1109 PlotAreaBuilder builder = 1110 PlotAreaBuilder.newBuilder().plotManager(manager).generatorName(worldConfig.getString( 1111 "generator.init", 1112 manager 1113 )) 1114 .plotAreaType(ConfigurationUtil.getType(worldConfig)).terrainType(ConfigurationUtil.getTerrain( 1115 worldConfig)) 1116 .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null)).worldName(worldName); 1117 injector().getInstance(SetupUtils.class).setupWorld(builder); 1118 world = Bukkit.getWorld(worldName); 1119 } else { 1120 try { 1121 if (!this.plotAreaManager.hasPlotArea(worldName)) { 1122 SetGenCB.setGenerator(BukkitUtil.getWorld(worldName)); 1123 } 1124 } catch (final Exception e) { 1125 LOGGER.error("Failed to reload world: {} | {}", world, e.getMessage()); 1126 Bukkit.getServer().unloadWorld(world, false); 1127 return; 1128 } 1129 } 1130 assert world != null; 1131 ChunkGenerator gen = world.getGenerator(); 1132 if (gen instanceof BukkitPlotGenerator) { 1133 PlotSquared.get().loadWorld(worldName, (BukkitPlotGenerator) gen); 1134 } else if (gen != null) { 1135 PlotSquared.get().loadWorld(worldName, new BukkitPlotGenerator(worldName, gen, this.plotAreaManager)); 1136 } else if (this.worldConfiguration.contains("worlds." + worldName)) { 1137 PlotSquared.get().loadWorld(worldName, null); 1138 } 1139 } 1140 1141 @Override 1142 public @NonNull String serverNativePackage() { 1143 final String name = Bukkit.getServer().getClass().getPackage().getName(); 1144 return name.substring(name.lastIndexOf('.') + 1); 1145 } 1146 1147 @Override 1148 public @NonNull GeneratorWrapper<?> wrapPlotGenerator( 1149 final @NonNull String world, 1150 final @NonNull IndependentPlotGenerator generator 1151 ) { 1152 return new BukkitPlotGenerator(world, generator, this.plotAreaManager); 1153 } 1154 1155 @Override 1156 public @NonNull String pluginsFormatted() { 1157 StringBuilder msg = new StringBuilder(); 1158 List<Plugin> plugins = new ArrayList<>(); 1159 Collections.addAll(plugins, Bukkit.getServer().getPluginManager().getPlugins()); 1160 plugins.sort(Comparator.comparing(Plugin::getName)); 1161 msg.append("Plugins (").append(plugins.size()).append("): \n"); 1162 for (Plugin p : plugins) { 1163 msg.append(" - ").append(p.getName()).append(":").append("\n") 1164 .append(" • Version: ").append(p.getDescription().getVersion()).append("\n") 1165 .append(" • Enabled: ").append(p.isEnabled()).append("\n") 1166 .append(" • Main: ").append(p.getDescription().getMain()).append("\n") 1167 .append(" • Authors: ").append(p.getDescription().getAuthors()).append("\n") 1168 .append(" • Load Before: ").append(p.getDescription().getLoadBefore()).append("\n") 1169 .append(" • Dependencies: ").append(p.getDescription().getDepend()).append("\n") 1170 .append(" • Soft Dependencies: ").append(p.getDescription().getSoftDepend()).append("\n"); 1171 } 1172 return msg.toString(); 1173 } 1174 1175 @Override 1176 @SuppressWarnings("ConstantConditions") 1177 public @NonNull String worldEditImplementations() { 1178 StringBuilder msg = new StringBuilder(); 1179 if (Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null) { 1180 msg.append("FastAsyncWorldEdit: ").append(Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit").getDescription().getVersion()); 1181 } else if (Bukkit.getPluginManager().getPlugin("AsyncWorldEdit") != null) { 1182 msg.append("AsyncWorldEdit: ").append(Bukkit.getPluginManager().getPlugin("AsyncWorldEdit").getDescription().getVersion()).append("\n"); 1183 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1184 } else { 1185 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1186 } 1187 return msg.toString(); 1188 } 1189 1190 @Override 1191 public com.plotsquared.core.location.@NonNull World<?> getPlatformWorld(final @NonNull String worldName) { 1192 return BukkitWorld.of(worldName); 1193 } 1194 1195 @Override 1196 public @NonNull Audience consoleAudience() { 1197 return BukkitUtil.BUKKIT_AUDIENCES.console(); 1198 } 1199 1200 @Override 1201 public @NonNull String pluginName() { 1202 return this.pluginName; 1203 } 1204 1205 public SingleWorldListener getSingleWorldListener() { 1206 return this.singleWorldListener; 1207 } 1208 1209 @Override 1210 public @NonNull Injector injector() { 1211 return this.injector; 1212 } 1213 1214 @Override 1215 public @NonNull PlotAreaManager plotAreaManager() { 1216 return this.plotAreaManager; 1217 } 1218 1219 @NonNull 1220 @Override 1221 public Locale getLocale() { 1222 return this.serverLocale; 1223 } 1224 1225 @Override 1226 public void setLocale(final @NonNull Locale locale) { 1227 throw new UnsupportedOperationException("Cannot replace server locale"); 1228 } 1229 1230 @Override 1231 public @NonNull PlatformWorldManager<?> worldManager() { 1232 return injector().getInstance(Key.get(new TypeLiteral<PlatformWorldManager<World>>() { 1233 })); 1234 } 1235 1236 @Override 1237 @NonNull 1238 @SuppressWarnings("unchecked") 1239 public PlayerManager<? extends PlotPlayer<Player>, ? extends Player> playerManager() { 1240 return (PlayerManager<BukkitPlayer, Player>) injector().getInstance(PlayerManager.class); 1241 } 1242 1243 @Override 1244 public void copyCaptionMaps() { 1245 /* Make this prettier at some point */ 1246 final String[] languages = new String[]{"en"}; 1247 for (final String language : languages) { 1248 if (!new File(new File(this.getDataFolder(), "lang"), String.format("messages_%s.json", language)).exists()) { 1249 this.saveResource(String.format("lang/messages_%s.json", language), false); 1250 LOGGER.info("Copied language file 'messages_{}.json'", language); 1251 } 1252 } 1253 } 1254 1255 @NonNull 1256 @Override 1257 public String toLegacyPlatformString(final @NonNull Component component) { 1258 return LegacyComponentSerializer.legacyAmpersand().serialize(component); 1259 } 1260 1261 @Override 1262 public boolean isFaweHooking() { 1263 return faweHook; 1264 } 1265 1266}