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.task.BukkitTaskManager; 054import com.plotsquared.bukkit.util.task.PaperTimeConverter; 055import com.plotsquared.bukkit.util.task.SpigotTimeConverter; 056import com.plotsquared.bukkit.uuid.EssentialsUUIDService; 057import com.plotsquared.bukkit.uuid.LuckPermsUUIDService; 058import com.plotsquared.bukkit.uuid.OfflinePlayerUUIDService; 059import com.plotsquared.bukkit.uuid.PaperUUIDService; 060import com.plotsquared.bukkit.uuid.SQLiteUUIDService; 061import com.plotsquared.bukkit.uuid.SquirrelIdUUIDService; 062import com.plotsquared.core.PlotPlatform; 063import com.plotsquared.core.PlotSquared; 064import com.plotsquared.core.backup.BackupManager; 065import com.plotsquared.core.components.ComponentPresetManager; 066import com.plotsquared.core.configuration.ConfigurationNode; 067import com.plotsquared.core.configuration.ConfigurationSection; 068import com.plotsquared.core.configuration.ConfigurationUtil; 069import com.plotsquared.core.configuration.Settings; 070import com.plotsquared.core.configuration.Storage; 071import com.plotsquared.core.configuration.caption.ChatFormatter; 072import com.plotsquared.core.configuration.file.YamlConfiguration; 073import com.plotsquared.core.database.DBFunc; 074import com.plotsquared.core.events.RemoveRoadEntityEvent; 075import com.plotsquared.core.events.Result; 076import com.plotsquared.core.generator.GeneratorWrapper; 077import com.plotsquared.core.generator.IndependentPlotGenerator; 078import com.plotsquared.core.generator.SingleWorldGenerator; 079import com.plotsquared.core.inject.annotations.BackgroundPipeline; 080import com.plotsquared.core.inject.annotations.DefaultGenerator; 081import com.plotsquared.core.inject.annotations.ImpromptuPipeline; 082import com.plotsquared.core.inject.annotations.WorldConfig; 083import com.plotsquared.core.inject.annotations.WorldFile; 084import com.plotsquared.core.inject.modules.PlotSquaredModule; 085import com.plotsquared.core.listener.PlotListener; 086import com.plotsquared.core.listener.WESubscriber; 087import com.plotsquared.core.player.PlotPlayer; 088import com.plotsquared.core.plot.Plot; 089import com.plotsquared.core.plot.PlotArea; 090import com.plotsquared.core.plot.PlotAreaTerrainType; 091import com.plotsquared.core.plot.PlotAreaType; 092import com.plotsquared.core.plot.PlotId; 093import com.plotsquared.core.plot.comment.CommentManager; 094import com.plotsquared.core.plot.flag.implementations.ServerPlotFlag; 095import com.plotsquared.core.plot.world.PlotAreaManager; 096import com.plotsquared.core.plot.world.SinglePlotArea; 097import com.plotsquared.core.plot.world.SinglePlotAreaManager; 098import com.plotsquared.core.setup.PlotAreaBuilder; 099import com.plotsquared.core.setup.SettingsNodesWrapper; 100import com.plotsquared.core.util.EventDispatcher; 101import com.plotsquared.core.util.FileUtils; 102import com.plotsquared.core.util.PlatformWorldManager; 103import com.plotsquared.core.util.PlayerManager; 104import com.plotsquared.core.util.PremiumVerification; 105import com.plotsquared.core.util.ReflectionUtils; 106import com.plotsquared.core.util.SetupUtils; 107import com.plotsquared.core.util.WorldUtil; 108import com.plotsquared.core.util.task.TaskManager; 109import com.plotsquared.core.util.task.TaskTime; 110import com.plotsquared.core.uuid.CacheUUIDService; 111import com.plotsquared.core.uuid.UUIDPipeline; 112import com.plotsquared.core.uuid.offline.OfflineModeUUIDService; 113import com.sk89q.worldedit.WorldEdit; 114import com.sk89q.worldedit.bukkit.BukkitAdapter; 115import io.papermc.lib.PaperLib; 116import net.kyori.adventure.audience.Audience; 117import net.kyori.adventure.text.Component; 118import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 119import org.apache.logging.log4j.LogManager; 120import org.apache.logging.log4j.Logger; 121import org.bstats.bukkit.Metrics; 122import org.bstats.charts.DrilldownPie; 123import org.bstats.charts.SimplePie; 124import org.bukkit.Bukkit; 125import org.bukkit.Chunk; 126import org.bukkit.Location; 127import org.bukkit.World; 128import org.bukkit.command.PluginCommand; 129import org.bukkit.entity.Entity; 130import org.bukkit.entity.LivingEntity; 131import org.bukkit.entity.Player; 132import org.bukkit.event.Listener; 133import org.bukkit.generator.ChunkGenerator; 134import org.bukkit.metadata.FixedMetadataValue; 135import org.bukkit.metadata.MetadataValue; 136import org.bukkit.plugin.Plugin; 137import org.bukkit.plugin.java.JavaPlugin; 138import org.checkerframework.checker.nullness.qual.NonNull; 139import org.checkerframework.checker.nullness.qual.Nullable; 140import org.incendo.serverlib.ServerLib; 141 142import java.io.File; 143import java.io.IOException; 144import java.lang.reflect.Method; 145import java.util.ArrayList; 146import java.util.Arrays; 147import java.util.Collections; 148import java.util.Comparator; 149import java.util.HashMap; 150import java.util.HashSet; 151import java.util.Iterator; 152import java.util.List; 153import java.util.Locale; 154import java.util.Map; 155import java.util.Queue; 156import java.util.Set; 157import java.util.UUID; 158import java.util.concurrent.ExecutionException; 159import java.util.concurrent.Executors; 160import java.util.concurrent.LinkedBlockingQueue; 161import java.util.concurrent.TimeUnit; 162 163import static com.plotsquared.core.util.PremiumVerification.getDownloadID; 164import static com.plotsquared.core.util.PremiumVerification.getResourceID; 165import static com.plotsquared.core.util.PremiumVerification.getUserID; 166import static com.plotsquared.core.util.ReflectionUtils.getRefClass; 167 168@SuppressWarnings("unused") 169@Singleton 170public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPlatform<Player> { 171 172 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + BukkitPlatform.class.getSimpleName()); 173 private static final int BSTATS_ID = 1404; 174 175 static { 176 try { 177 Settings.load(new File(PlotSquared.platform().getDirectory(), "settings.yml")); 178 } catch (Throwable ignored) { 179 } 180 } 181 182 private int[] version; 183 private String pluginName; 184 private SingleWorldListener singleWorldListener; 185 private Method methodUnloadChunk0; 186 private boolean methodUnloadSetup = false; 187 private boolean metricsStarted; 188 private boolean faweHook = false; 189 190 private Injector injector; 191 192 @Inject 193 private PlotAreaManager plotAreaManager; 194 @Inject 195 private EventDispatcher eventDispatcher; 196 @Inject 197 private PlotListener plotListener; 198 @Inject 199 @WorldConfig 200 private YamlConfiguration worldConfiguration; 201 @Inject 202 @WorldFile 203 private File worldfile; 204 @Inject 205 private BukkitPlayerManager playerManager; 206 @Inject 207 private BackupManager backupManager; 208 @Inject 209 @ImpromptuPipeline 210 private UUIDPipeline impromptuPipeline; 211 @Inject 212 @BackgroundPipeline 213 private UUIDPipeline backgroundPipeline; 214 @Inject 215 private PlatformWorldManager<World> worldManager; 216 private Locale serverLocale; 217 218 @SuppressWarnings("StringSplitter") 219 @Override 220 public int @NonNull [] serverVersion() { 221 if (this.version == null) { 222 try { 223 this.version = new int[3]; 224 String[] split = Bukkit.getBukkitVersion().split("-")[0].split("\\."); 225 this.version[0] = Integer.parseInt(split[0]); 226 this.version[1] = Integer.parseInt(split[1]); 227 if (split.length == 3) { 228 this.version[2] = Integer.parseInt(split[2]); 229 } 230 } catch (NumberFormatException e) { 231 e.printStackTrace(); 232 return new int[]{1, 13, 0}; 233 } 234 } 235 return this.version; 236 } 237 238 @Override 239 public int versionMinHeight() { 240 return serverVersion()[1] >= 18 ? -64 : 0; 241 } 242 243 @Override 244 public int versionMaxHeight() { 245 return serverVersion()[1] >= 18 ? 319 : 255; 246 } 247 248 @Override 249 public @NonNull String serverImplementation() { 250 return Bukkit.getVersion(); 251 } 252 253 @Override 254 public void onEnable() { 255 this.pluginName = getDescription().getName(); 256 257 final TaskTime.TimeConverter timeConverter; 258 if (PaperLib.isPaper()) { 259 timeConverter = new PaperTimeConverter(); 260 } else { 261 timeConverter = new SpigotTimeConverter(); 262 } 263 264 // Stuff that needs to be created before the PlotSquared instance 265 PlotPlayer.registerConverter(Player.class, BukkitUtil::adapt); 266 TaskManager.setPlatformImplementation(new BukkitTaskManager(this, timeConverter)); 267 268 final PlotSquared plotSquared = new PlotSquared(this, "Bukkit"); 269 270 // FastAsyncWorldEdit 271 if (Settings.FAWE_Components.FAWE_HOOK) { 272 Plugin fawe = getServer().getPluginManager().getPlugin("FastAsyncWorldEdit"); 273 if (fawe != null) { 274 try { 275 Class.forName("com.fastasyncworldedit.bukkit.regions.plotsquared.FaweQueueCoordinator"); 276 faweHook = true; 277 } catch (Exception ignored) { 278 LOGGER.error("Incompatible version of FastAsyncWorldEdit to enable hook, please upgrade: https://ci.athion" + 279 ".net/job/FastAsyncWorldEdit/"); 280 } 281 } 282 } 283 284 // We create the injector after PlotSquared has been initialized, so that we have access 285 // to generated instances and settings 286 this.injector = Guice 287 .createInjector( 288 Stage.PRODUCTION, 289 new PermissionModule(), 290 new WorldManagerModule(), 291 new PlotSquaredModule(), 292 new BukkitModule(this), 293 new BackupModule() 294 ); 295 this.injector.injectMembers(this); 296 297 try { 298 this.injector.getInstance(TranslationUpdateManager.class).upgradeTranslationFile(); 299 } catch (IOException e) { 300 throw new RuntimeException(e); 301 } 302 303 this.serverLocale = Locale.forLanguageTag(Settings.Enabled_Components.DEFAULT_LOCALE); 304 305 /* TODO Enable update checker before v7 is released to GA 306 if (PremiumVerification.isPremium() && Settings.Enabled_Components.UPDATE_NOTIFICATIONS) { 307 injector.getInstance(UpdateUtility.class).updateChecker(); 308 } 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(1L)); 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 @Override 1165 public @NonNull String pluginsFormatted() { 1166 StringBuilder msg = new StringBuilder(); 1167 List<Plugin> plugins = new ArrayList<>(); 1168 Collections.addAll(plugins, Bukkit.getServer().getPluginManager().getPlugins()); 1169 plugins.sort(Comparator.comparing(Plugin::getName)); 1170 msg.append("Plugins (").append(plugins.size()).append("): \n"); 1171 for (Plugin p : plugins) { 1172 msg.append(" - ").append(p.getName()).append(":").append("\n") 1173 .append(" • Version: ").append(p.getDescription().getVersion()).append("\n") 1174 .append(" • Enabled: ").append(p.isEnabled()).append("\n") 1175 .append(" • Main: ").append(p.getDescription().getMain()).append("\n") 1176 .append(" • Authors: ").append(p.getDescription().getAuthors()).append("\n") 1177 .append(" • Load Before: ").append(p.getDescription().getLoadBefore()).append("\n") 1178 .append(" • Dependencies: ").append(p.getDescription().getDepend()).append("\n") 1179 .append(" • Soft Dependencies: ").append(p.getDescription().getSoftDepend()).append("\n"); 1180 } 1181 return msg.toString(); 1182 } 1183 1184 @Override 1185 @SuppressWarnings("ConstantConditions") 1186 public @NonNull String worldEditImplementations() { 1187 StringBuilder msg = new StringBuilder(); 1188 if (Bukkit.getPluginManager().getPlugin("FastAsyncWorldEdit") != null) { 1189 msg.append("FastAsyncWorldEdit: ").append(Bukkit 1190 .getPluginManager() 1191 .getPlugin("FastAsyncWorldEdit") 1192 .getDescription() 1193 .getVersion()); 1194 } else if (Bukkit.getPluginManager().getPlugin("AsyncWorldEdit") != null) { 1195 msg.append("AsyncWorldEdit: ").append(Bukkit 1196 .getPluginManager() 1197 .getPlugin("AsyncWorldEdit") 1198 .getDescription() 1199 .getVersion()).append("\n"); 1200 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1201 } else { 1202 msg.append("WorldEdit: ").append(Bukkit.getPluginManager().getPlugin("WorldEdit").getDescription().getVersion()); 1203 } 1204 return msg.toString(); 1205 } 1206 1207 @Override 1208 public com.plotsquared.core.location.@NonNull World<?> getPlatformWorld(final @NonNull String worldName) { 1209 return BukkitWorld.of(worldName); 1210 } 1211 1212 @Override 1213 public @NonNull Audience consoleAudience() { 1214 return BukkitUtil.BUKKIT_AUDIENCES.console(); 1215 } 1216 1217 @Override 1218 public @NonNull String pluginName() { 1219 return this.pluginName; 1220 } 1221 1222 public SingleWorldListener getSingleWorldListener() { 1223 return this.singleWorldListener; 1224 } 1225 1226 @Override 1227 public @NonNull Injector injector() { 1228 return this.injector; 1229 } 1230 1231 @Override 1232 public @NonNull PlotAreaManager plotAreaManager() { 1233 return this.plotAreaManager; 1234 } 1235 1236 @NonNull 1237 @Override 1238 public Locale getLocale() { 1239 return this.serverLocale; 1240 } 1241 1242 @Override 1243 public void setLocale(final @NonNull Locale locale) { 1244 throw new UnsupportedOperationException("Cannot replace server locale"); 1245 } 1246 1247 @Override 1248 public @NonNull PlatformWorldManager<?> worldManager() { 1249 return injector().getInstance(Key.get(new TypeLiteral<PlatformWorldManager<World>>() { 1250 })); 1251 } 1252 1253 @Override 1254 @NonNull 1255 @SuppressWarnings("unchecked") 1256 public PlayerManager<? extends PlotPlayer<Player>, ? extends Player> playerManager() { 1257 return (PlayerManager<BukkitPlayer, Player>) injector().getInstance(PlayerManager.class); 1258 } 1259 1260 @Override 1261 public void copyCaptionMaps() { 1262 /* Make this prettier at some point */ 1263 final String[] languages = new String[]{"en"}; 1264 for (final String language : languages) { 1265 if (!new File(new File(this.getDataFolder(), "lang"), String.format("messages_%s.json", language)).exists()) { 1266 this.saveResource(String.format("lang/messages_%s.json", language), false); 1267 LOGGER.info("Copied language file 'messages_{}.json'", language); 1268 } 1269 } 1270 } 1271 1272 @NonNull 1273 @Override 1274 public String toLegacyPlatformString(final @NonNull Component component) { 1275 return LegacyComponentSerializer.legacyAmpersand().serialize(component); 1276 } 1277 1278 @Override 1279 public boolean isFaweHooking() { 1280 return faweHook; 1281 } 1282 1283}