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