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