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 //noinspection ConstantValue - getEntitySpawnReason annotated as NotNull, but is not NotNull. lol. 783 if (PaperLib.isPaper() && entity.getEntitySpawnReason() != null && "CUSTOM".equals(entity.getEntitySpawnReason().name())) { 784 continue; 785 } 786 // Fallback for Spigot not having Entity#getEntitySpawnReason 787 if (entity.getMetadata("ps_custom_spawned").stream().anyMatch(MetadataValue::asBoolean)) { 788 continue; 789 } 790 switch (entity.getType().toString()) { 791 case "EGG": 792 case "FISHING_HOOK": 793 case "ENDER_SIGNAL": 794 case "AREA_EFFECT_CLOUD": 795 case "EXPERIENCE_ORB": 796 case "LEASH_HITCH": 797 case "FIREWORK": 798 case "LIGHTNING": 799 case "WITHER_SKULL": 800 case "UNKNOWN": 801 case "PLAYER": 802 // non moving / unmovable 803 continue; 804 case "THROWN_EXP_BOTTLE": 805 case "SPLASH_POTION": 806 case "SNOWBALL": 807 case "SHULKER_BULLET": 808 case "SPECTRAL_ARROW": 809 case "ENDER_PEARL": 810 case "ARROW": 811 case "LLAMA_SPIT": 812 case "TRIDENT": 813 // managed elsewhere | projectile 814 continue; 815 case "ITEM_FRAME": 816 case "PAINTING": 817 // Not vehicles 818 continue; 819 case "ARMOR_STAND": 820 // Temporarily classify as vehicle 821 case "MINECART": 822 case "MINECART_CHEST": 823 case "MINECART_COMMAND": 824 case "MINECART_FURNACE": 825 case "MINECART_HOPPER": 826 case "MINECART_MOB_SPAWNER": 827 case "ENDER_CRYSTAL": 828 case "MINECART_TNT": 829 case "CHEST_BOAT": 830 case "BOAT": 831 if (Settings.Enabled_Components.KILL_ROAD_VEHICLES) { 832 com.plotsquared.core.location.Location location = BukkitUtil.adapt(entity.getLocation()); 833 Plot plot = location.getPlot(); 834 if (plot == null) { 835 if (location.isPlotArea()) { 836 if (entity.hasMetadata("ps-tmp-teleport")) { 837 continue; 838 } 839 this.removeRoadEntity(entity, iterator); 840 } 841 continue; 842 } 843 List<MetadataValue> meta = entity.getMetadata("plot"); 844 if (meta.isEmpty()) { 845 continue; 846 } 847 Plot origin = (Plot) meta.get(0).value(); 848 if (!plot.equals(origin.getBasePlot(false))) { 849 if (entity.hasMetadata("ps-tmp-teleport")) { 850 continue; 851 } 852 this.removeRoadEntity(entity, iterator); 853 } 854 } 855 continue; 856 case "SMALL_FIREBALL": 857 case "FIREBALL": 858 case "DRAGON_FIREBALL": 859 case "DROPPED_ITEM": 860 if (Settings.Enabled_Components.KILL_ROAD_ITEMS 861 && plotArea.getOwnedPlotAbs(BukkitUtil.adapt(entity.getLocation())) == null) { 862 this.removeRoadEntity(entity, iterator); 863 } 864 // dropped item 865 continue; 866 case "PRIMED_TNT": 867 case "FALLING_BLOCK": 868 // managed elsewhere 869 continue; 870 case "SHULKER": 871 if (Settings.Enabled_Components.KILL_ROAD_MOBS && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS || entity.getCustomName() == null)) { 872 LivingEntity livingEntity = (LivingEntity) entity; 873 List<MetadataValue> meta = entity.getMetadata("shulkerPlot"); 874 if (!meta.isEmpty()) { 875 if (livingEntity.isLeashed() && !Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS) { 876 continue; 877 } 878 if (entity.hasMetadata("keep")) { 879 continue; 880 } 881 882 PlotId originalPlotId = (PlotId) meta.get(0).value(); 883 if (originalPlotId != null) { 884 com.plotsquared.core.location.Location pLoc = BukkitUtil.adapt(entity.getLocation()); 885 PlotArea area = pLoc.getPlotArea(); 886 if (area != null) { 887 Plot currentPlot = area.getPlotAbs(pLoc); 888 if (currentPlot == null || !originalPlotId.equals(currentPlot.getId())) { 889 if (entity.hasMetadata("ps-tmp-teleport")) { 890 continue; 891 } 892 this.removeRoadEntity(entity, iterator); 893 } 894 } 895 } 896 } else { 897 //This is to apply the metadata to already spawned shulkers (see EntitySpawnListener.java) 898 com.plotsquared.core.location.Location pLoc = BukkitUtil.adapt(entity.getLocation()); 899 PlotArea area = pLoc.getPlotArea(); 900 if (area != null) { 901 Plot currentPlot = area.getPlotAbs(pLoc); 902 if (currentPlot != null) { 903 entity.setMetadata( 904 "shulkerPlot", 905 new FixedMetadataValue((Plugin) PlotSquared.platform(), currentPlot.getId()) 906 ); 907 } 908 } 909 } 910 } 911 continue; 912 case "ZOMBIFIED_PIGLIN": 913 case "PIGLIN_BRUTE": 914 case "LLAMA": 915 case "DONKEY": 916 case "MULE": 917 case "ZOMBIE_HORSE": 918 case "SKELETON_HORSE": 919 case "HUSK": 920 case "ELDER_GUARDIAN": 921 case "WITHER_SKELETON": 922 case "STRAY": 923 case "ZOMBIE_VILLAGER": 924 case "EVOKER": 925 case "EVOKER_FANGS": 926 case "VEX": 927 case "VINDICATOR": 928 case "POLAR_BEAR": 929 case "BAT": 930 case "BLAZE": 931 case "CAVE_SPIDER": 932 case "CHICKEN": 933 case "COW": 934 case "CREEPER": 935 case "ENDERMAN": 936 case "ENDERMITE": 937 case "ENDER_DRAGON": 938 case "GHAST": 939 case "GIANT": 940 case "GUARDIAN": 941 case "HORSE": 942 case "IRON_GOLEM": 943 case "MAGMA_CUBE": 944 case "MUSHROOM_COW": 945 case "OCELOT": 946 case "PIG": 947 case "PIG_ZOMBIE": 948 case "RABBIT": 949 case "SHEEP": 950 case "SILVERFISH": 951 case "SKELETON": 952 case "SLIME": 953 case "SNOWMAN": 954 case "SPIDER": 955 case "SQUID": 956 case "VILLAGER": 957 case "WITCH": 958 case "WITHER": 959 case "WOLF": 960 case "ZOMBIE": 961 case "PARROT": 962 case "SALMON": 963 case "DOLPHIN": 964 case "TROPICAL_FISH": 965 case "DROWNED": 966 case "COD": 967 case "TURTLE": 968 case "PUFFERFISH": 969 case "PHANTOM": 970 case "ILLUSIONER": 971 case "CAT": 972 case "PANDA": 973 case "FOX": 974 case "PILLAGER": 975 case "TRADER_LLAMA": 976 case "WANDERING_TRADER": 977 case "RAVAGER": 978 case "BEE": 979 case "HOGLIN": 980 case "PIGLIN": 981 case "ZOGLIN": 982 default: { 983 if (Settings.Enabled_Components.KILL_ROAD_MOBS) { 984 Location location = entity.getLocation(); 985 if (BukkitUtil.adapt(location).isPlotRoad()) { 986 if (entity instanceof LivingEntity livingEntity) { 987 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS || !livingEntity.isLeashed()) 988 || !entity.hasMetadata("keep")) { 989 Entity passenger = entity.getPassenger(); 990 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS 991 || !((passenger instanceof Player) || livingEntity.isLeashed())) 992 && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS || entity.getCustomName() == null) 993 && entity.getMetadata("keep").isEmpty()) { 994 if (entity.hasMetadata("ps-tmp-teleport")) { 995 continue; 996 } 997 this.removeRoadEntity(entity, iterator); 998 } 999 } 1000 } else { 1001 Entity passenger = entity.getPassenger(); 1002 if ((Settings.Enabled_Components.KILL_OWNED_ROAD_MOBS || !(passenger instanceof Player)) 1003 && (Settings.Enabled_Components.KILL_NAMED_ROAD_MOBS && entity.getCustomName() != null) 1004 && entity.getMetadata("keep").isEmpty()) { 1005 if (entity.hasMetadata("ps-tmp-teleport")) { 1006 continue; 1007 } 1008 this.removeRoadEntity(entity, iterator); 1009 } 1010 } 1011 } 1012 } 1013 } 1014 } 1015 } 1016 } catch (Throwable e) { 1017 e.printStackTrace(); 1018 } 1019 }), TaskTime.seconds(1L)); 1020 } 1021 1022 private void removeRoadEntity(Entity entity, Iterator<Entity> entityIterator) { 1023 RemoveRoadEntityEvent event = eventDispatcher.callRemoveRoadEntity(BukkitAdapter.adapt(entity)); 1024 1025 if (event.getEventResult() == Result.DENY) { 1026 return; 1027 } 1028 1029 entityIterator.remove(); 1030 entity.remove(); 1031 } 1032 1033 @Override 1034 public @Nullable 1035 final ChunkGenerator getDefaultWorldGenerator( 1036 final @NonNull String worldName, 1037 final @Nullable String id 1038 ) { 1039 final IndependentPlotGenerator result; 1040 if (id != null && id.equalsIgnoreCase("single")) { 1041 result = injector().getInstance(SingleWorldGenerator.class); 1042 } else { 1043 result = injector().getInstance(Key.get(IndependentPlotGenerator.class, DefaultGenerator.class)); 1044 if (!PlotSquared.get().setupPlotWorld(worldName, id, result)) { 1045 return null; 1046 } 1047 } 1048 return (ChunkGenerator) result.specify(worldName); 1049 } 1050 1051 @Override 1052 public @Nullable GeneratorWrapper<?> getGenerator( 1053 final @NonNull String world, 1054 final @Nullable String name 1055 ) { 1056 if (name == null) { 1057 return null; 1058 } 1059 final Plugin genPlugin = Bukkit.getPluginManager().getPlugin(name); 1060 if (genPlugin != null && genPlugin.isEnabled()) { 1061 ChunkGenerator gen = genPlugin.getDefaultWorldGenerator(world, ""); 1062 if (gen instanceof GeneratorWrapper<?>) { 1063 return (GeneratorWrapper<?>) gen; 1064 } 1065 return new BukkitPlotGenerator(world, gen, this.plotAreaManager); 1066 } else { 1067 return new BukkitPlotGenerator( 1068 world, 1069 injector().getInstance(Key.get(IndependentPlotGenerator.class, DefaultGenerator.class)), 1070 this.plotAreaManager 1071 ); 1072 } 1073 } 1074 1075 @Override 1076 public void startMetrics() { 1077 if (this.metricsStarted) { 1078 return; 1079 } 1080 this.metricsStarted = true; 1081 Metrics metrics = new Metrics(this, BSTATS_ID); // bstats 1082 metrics.addCustomChart(new DrilldownPie("area_types", () -> { 1083 final Map<String, Map<String, Integer>> map = new HashMap<>(); 1084 for (final PlotAreaType plotAreaType : PlotAreaType.values()) { 1085 final Map<String, Integer> terrainTypes = new HashMap<>(); 1086 for (final PlotAreaTerrainType plotAreaTerrainType : PlotAreaTerrainType.values()) { 1087 terrainTypes.put(plotAreaTerrainType.name().toLowerCase(), 0); 1088 } 1089 map.put(plotAreaType.name().toLowerCase(), terrainTypes); 1090 } 1091 for (final PlotArea plotArea : this.plotAreaManager.getAllPlotAreas()) { 1092 final Map<String, Integer> terrainTypeMap = map.get(plotArea.getType().name().toLowerCase()); 1093 terrainTypeMap.put( 1094 plotArea.getTerrain().name().toLowerCase(), 1095 terrainTypeMap.get(plotArea.getTerrain().name().toLowerCase()) + 1 1096 ); 1097 } 1098 return map; 1099 })); 1100 metrics.addCustomChart(new SimplePie( 1101 "premium", 1102 () -> PremiumVerification.isPremium() ? "Premium" : "Non-Premium" 1103 )); 1104 metrics.addCustomChart(new SimplePie("worlds", () -> Settings.Enabled_Components.WORLDS ? "true" : "false")); 1105 metrics.addCustomChart(new SimplePie("economy", () -> Settings.Enabled_Components.ECONOMY ? "true" : "false")); 1106 metrics.addCustomChart(new SimplePie( 1107 "plot_expiry", 1108 () -> Settings.Enabled_Components.PLOT_EXPIRY ? "true" : "false" 1109 )); 1110 metrics.addCustomChart(new SimplePie("database_type", () -> Storage.MySQL.USE ? "MySQL" : "SQLite")); 1111 metrics.addCustomChart(new SimplePie( 1112 "worldedit_implementation", 1113 () -> Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null ? "FastAsyncWorldEdit" : "WorldEdit" 1114 )); 1115 metrics.addCustomChart(new SimplePie("offline_mode", () -> Settings.UUID.OFFLINE ? "true" : "false")); 1116 metrics.addCustomChart(new SimplePie("offline_mode_force", () -> Settings.UUID.FORCE_LOWERCASE ? "true" : "false")); 1117 } 1118 1119 @Override 1120 public void unregister(final @NonNull PlotPlayer<?> player) { 1121 PlotSquared.platform().playerManager().removePlayer(player.getUUID()); 1122 } 1123 1124 @Override 1125 public void setGenerator(final @NonNull String worldName) { 1126 World world = BukkitUtil.getWorld(worldName); 1127 if (world == null) { 1128 // create world 1129 ConfigurationSection worldConfig = this.worldConfiguration.getConfigurationSection("worlds." + worldName); 1130 String manager = worldConfig.getString("generator.plugin", pluginName()); 1131 PlotAreaBuilder builder = 1132 PlotAreaBuilder.newBuilder().plotManager(manager).generatorName(worldConfig.getString( 1133 "generator.init", 1134 manager 1135 )) 1136 .plotAreaType(ConfigurationUtil.getType(worldConfig)).terrainType(ConfigurationUtil.getTerrain( 1137 worldConfig)) 1138 .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null)).worldName(worldName); 1139 injector().getInstance(SetupUtils.class).setupWorld(builder); 1140 world = Bukkit.getWorld(worldName); 1141 } else { 1142 try { 1143 if (!this.plotAreaManager.hasPlotArea(worldName)) { 1144 SetGenCB.setGenerator(BukkitUtil.getWorld(worldName)); 1145 } 1146 } catch (final Exception e) { 1147 LOGGER.error("Failed to reload world: {} | {}", world, e.getMessage()); 1148 Bukkit.getServer().unloadWorld(world, false); 1149 return; 1150 } 1151 } 1152 assert world != null; 1153 ChunkGenerator gen = world.getGenerator(); 1154 if (gen instanceof BukkitPlotGenerator) { 1155 PlotSquared.get().loadWorld(worldName, (BukkitPlotGenerator) gen); 1156 } else if (gen != null) { 1157 PlotSquared.get().loadWorld(worldName, new BukkitPlotGenerator(worldName, gen, this.plotAreaManager)); 1158 } else if (this.worldConfiguration.contains("worlds." + worldName)) { 1159 PlotSquared.get().loadWorld(worldName, null); 1160 } 1161 } 1162 1163 @Override 1164 public @NonNull String serverNativePackage() { 1165 final String name = Bukkit.getServer().getClass().getPackage().getName(); 1166 return name.substring(name.lastIndexOf('.') + 1); 1167 } 1168 1169 @Override 1170 public @NonNull GeneratorWrapper<?> wrapPlotGenerator( 1171 final @NonNull String world, 1172 final @NonNull IndependentPlotGenerator generator 1173 ) { 1174 return new BukkitPlotGenerator(world, generator, this.plotAreaManager); 1175 } 1176 1177 @SuppressWarnings("deprecation") // Paper deprecation 1178 @Override 1179 public @NonNull String pluginsFormatted() { 1180 StringBuilder msg = new StringBuilder(); 1181 List<Plugin> plugins = new ArrayList<>(); 1182 Collections.addAll(plugins, Bukkit.getServer().getPluginManager().getPlugins()); 1183 plugins.sort(Comparator.comparing(Plugin::getName)); 1184 msg.append("Plugins (").append(plugins.size()).append("): \n"); 1185 for (Plugin p : plugins) { 1186 msg.append(" - ").append(p.getName()).append(":").append("\n") 1187 .append(" • Version: ").append(p.getDescription().getVersion()).append("\n") 1188 .append(" • Enabled: ").append(p.isEnabled()).append("\n") 1189 .append(" • Main: ").append(p.getDescription().getMain()).append("\n") 1190 .append(" • Authors: ").append(p.getDescription().getAuthors()).append("\n") 1191 .append(" • Load Before: ").append(p.getDescription().getLoadBefore()).append("\n") 1192 .append(" • Dependencies: ").append(p.getDescription().getDepend()).append("\n") 1193 .append(" • Soft Dependencies: ").append(p.getDescription().getSoftDepend()).append("\n"); 1194 List<RegisteredServiceProvider<?>> providers = Bukkit.getServicesManager().getRegistrations(p); 1195 if (!providers.isEmpty()) { 1196 msg.append(" • Provided Services: \n"); 1197 for (RegisteredServiceProvider<?> provider : providers) { 1198 msg.append(" • ") 1199 .append(provider.getService().getName()).append(" = ") 1200 .append(provider.getProvider().getClass().getName()) 1201 .append(" (priority: ").append(provider.getPriority()).append(")") 1202 .append("\n"); 1203 } 1204 } 1205 } 1206 return msg.toString(); 1207 } 1208 1209 @Override 1210 @SuppressWarnings({"ConstantConditions", "deprecation"}) // Paper deprecation 1211 public @NonNull String worldEditImplementations() { 1212 StringBuilder msg = new StringBuilder(); 1213 if (Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null) { 1214 msg.append("FastAsyncWorldEdit: ").append(Bukkit 1215 .getPluginManager() 1216 .getPlugin("FastAsyncWorldEdit") 1217 .getDescription() 1218 .getVersion()); 1219 } else if (Bukkit.getPluginManager().getPlugin("AsyncWorldEdit") != null) { 1220 msg.append("AsyncWorldEdit: ").append(Bukkit 1221 .getPluginManager() 1222 .getPlugin("AsyncWorldEdit") 1223 .getDescription() 1224 .getVersion()).append("\n"); 1225 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1226 } else { 1227 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1228 } 1229 return msg.toString(); 1230 } 1231 1232 @Override 1233 public com.plotsquared.core.location.@NonNull World<?> getPlatformWorld(final @NonNull String worldName) { 1234 return BukkitWorld.of(worldName); 1235 } 1236 1237 @Override 1238 public @NonNull Audience consoleAudience() { 1239 return BukkitUtil.BUKKIT_AUDIENCES.console(); 1240 } 1241 1242 @Override 1243 public @NonNull String pluginName() { 1244 return this.pluginName; 1245 } 1246 1247 public SingleWorldListener getSingleWorldListener() { 1248 return this.singleWorldListener; 1249 } 1250 1251 @Override 1252 public @NonNull Injector injector() { 1253 return this.injector; 1254 } 1255 1256 @Override 1257 public @NonNull PlotAreaManager plotAreaManager() { 1258 return this.plotAreaManager; 1259 } 1260 1261 @NonNull 1262 @Override 1263 public Locale getLocale() { 1264 return this.serverLocale; 1265 } 1266 1267 @Override 1268 public void setLocale(final @NonNull Locale locale) { 1269 throw new UnsupportedOperationException("Cannot replace server locale"); 1270 } 1271 1272 @Override 1273 public @NonNull PlatformWorldManager<?> worldManager() { 1274 return injector().getInstance(Key.get(new TypeLiteral<PlatformWorldManager<World>>() { 1275 })); 1276 } 1277 1278 @Override 1279 @NonNull 1280 @SuppressWarnings("unchecked") 1281 public PlayerManager<? extends PlotPlayer<Player>, ? extends Player> playerManager() { 1282 return (PlayerManager<BukkitPlayer, Player>) injector().getInstance(PlayerManager.class); 1283 } 1284 1285 @Override 1286 public void copyCaptionMaps() { 1287 /* Make this prettier at some point */ 1288 final String[] languages = new String[]{"en"}; 1289 for (final String language : languages) { 1290 if (!new File(new File(this.getDataFolder(), "lang"), String.format("messages_%s.json", language)).exists()) { 1291 this.saveResource(String.format("lang/messages_%s.json", language), false); 1292 LOGGER.info("Copied language file 'messages_{}.json'", language); 1293 } 1294 } 1295 } 1296 1297 @NonNull 1298 @Override 1299 public String toLegacyPlatformString(final @NonNull Component component) { 1300 return LegacyComponentSerializer.legacyAmpersand().serialize(component); 1301 } 1302 1303 @Override 1304 public boolean isFaweHooking() { 1305 return faweHook; 1306 } 1307 1308}