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