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