From aadec3f09b0af0961b24ecfe9525cbe0bde41960 Mon Sep 17 00:00:00 2001 From: aleigh Date: Thu, 9 Sep 2021 13:54:06 -0700 Subject: [PATCH] sk89q-worldguard: Added as clone of commit cb69ab73 from their public gitlab --- sk89q-worldguard/.gitattributes | 2 + sk89q-worldguard/.github/FUNDING.yml | 1 + .../.github/ISSUE_TEMPLATE/bug_report.md | 34 + .../.github/ISSUE_TEMPLATE/config.yml | 5 + .../.github/ISSUE_TEMPLATE/feature_request.md | 20 + sk89q-worldguard/.gitignore | 19 + sk89q-worldguard/.travis.yml | 8 + sk89q-worldguard/CHANGELOG.md | 998 +++++++++ sk89q-worldguard/CONTRIBUTING.md | 68 + sk89q-worldguard/HEADER.txt | 16 + sk89q-worldguard/LICENSE.txt | 201 ++ sk89q-worldguard/README.md | 47 + sk89q-worldguard/build.gradle.kts | 26 + sk89q-worldguard/buildSrc/build.gradle.kts | 17 + .../src/main/kotlin/ArtifactoryConfig.kt | 40 + .../buildSrc/src/main/kotlin/CommonConfig.kt | 38 + .../buildSrc/src/main/kotlin/GradleExtras.kt | 12 + .../buildSrc/src/main/kotlin/LibsConfig.kt | 171 ++ .../src/main/kotlin/PlatformConfig.kt | 113 + .../buildSrc/src/main/kotlin/Versions.kt | 9 + .../config/checkstyle/checkstyle.xml | 63 + .../config/checkstyle/import-control.xml | 39 + .../config/checkstyle/suppressions.xml | 7 + sk89q-worldguard/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + sk89q-worldguard/gradlew | 188 ++ sk89q-worldguard/gradlew.bat | 100 + sk89q-worldguard/settings.gradle.kts | 7 + .../worldguard-bukkit/build.gradle.kts | 78 + .../contrib/blacklist_table.sql | 24 + .../contrib/region_manual_update_20110325.sql | 114 + .../bukkit/BukkitConfigurationManager.java | 129 ++ .../worldguard/bukkit/BukkitDebugHandler.java | 230 ++ .../bukkit/BukkitOfflinePlayer.java | 241 ++ .../sk89q/worldguard/bukkit/BukkitPlayer.java | 221 ++ .../bukkit/BukkitRegionContainer.java | 132 ++ .../bukkit/BukkitStringMatcher.java | 246 ++ .../sk89q/worldguard/bukkit/BukkitUtil.java | 113 + .../bukkit/BukkitWorldConfiguration.java | 448 ++++ .../bukkit/BukkitWorldGuardPlatform.java | 274 +++ .../worldguard/bukkit/ProtectionQuery.java | 153 ++ .../worldguard/bukkit/WorldGuardPlugin.java | 529 +++++ .../sk89q/worldguard/bukkit/cause/Cause.java | 310 +++ .../chest/BukkitSignChestProtection.java | 54 + .../worldguard/bukkit/event/BulkEvent.java | 39 + .../bukkit/event/DelegateEvent.java | 142 ++ .../bukkit/event/DelegateEvents.java | 72 + .../worldguard/bukkit/event/Handleable.java | 30 + .../event/block/AbstractBlockEvent.java | 186 ++ .../bukkit/event/block/BreakBlockEvent.java | 64 + .../bukkit/event/block/PlaceBlockEvent.java | 69 + .../bukkit/event/block/UseBlockEvent.java | 64 + .../bukkit/event/debug/CancelAttempt.java | 76 + .../bukkit/event/debug/CancelLogger.java | 54 + .../bukkit/event/debug/CancelLogging.java | 35 + .../event/debug/LoggingBlockBreakEvent.java | 46 + .../event/debug/LoggingBlockPlaceEvent.java | 48 + .../LoggingEntityDamageByEntityEvent.java | 45 + .../debug/LoggingPlayerInteractEvent.java | 62 + .../event/entity/AbstractEntityEvent.java | 106 + .../event/entity/DamageEntityEvent.java | 61 + .../event/entity/DestroyEntityEvent.java | 61 + .../bukkit/event/entity/SpawnEntityEvent.java | 73 + .../bukkit/event/entity/UseEntityEvent.java | 61 + .../bukkit/event/inventory/UseItemEvent.java | 80 + .../event/player/ProcessPlayerEvent.java | 59 + .../bukkit/internal/TargetMatcherSet.java | 84 + .../bukkit/internal/WGMetadata.java | 87 + .../bukkit/listener/AbstractListener.java | 155 ++ .../bukkit/listener/BlacklistListener.java | 390 ++++ .../listener/BlockedPotionsListener.java | 154 ++ .../listener/BuildPermissionListener.java | 208 ++ .../listener/ChestProtectionListener.java | 192 ++ .../bukkit/listener/DebuggingListener.java | 229 ++ .../listener/EventAbstractionListener.java | 1267 +++++++++++ .../listener/InvincibilityListener.java | 113 + .../bukkit/listener/PlayerModesListener.java | 83 + .../bukkit/listener/PlayerMoveListener.java | 166 ++ .../bukkit/listener/RegionFlagsListener.java | 153 ++ .../listener/RegionProtectionListener.java | 572 +++++ .../listener/WorldGuardBlockListener.java | 692 ++++++ .../WorldGuardCommandBookListener.java | 79 + .../listener/WorldGuardEntityListener.java | 853 +++++++ .../listener/WorldGuardHangingListener.java | 107 + .../listener/WorldGuardPlayerListener.java | 438 ++++ .../listener/WorldGuardServerListener.java | 46 + .../listener/WorldGuardVehicleListener.java | 78 + .../listener/WorldGuardWeatherListener.java | 97 + .../listener/WorldGuardWorldListener.java | 91 + .../bukkit/listener/WorldRulesListener.java | 63 + .../debounce/BlockPistonExtendKey.java | 59 + .../debounce/BlockPistonRetractKey.java | 55 + .../listener/debounce/EventDebounce.java | 89 + .../legacy/AbstractEventDebounce.java | 85 + .../legacy/BlockEntityEventDebounce.java | 73 + .../legacy/EntityEntityEventDebounce.java | 67 + .../InventoryMoveItemEventDebounce.java | 123 + .../protection/events/DisallowedPVPEvent.java | 81 + .../events/flags/FlagContextCreateEvent.java | 56 + .../bukkit/session/BukkitSessionManager.java | 99 + .../bukkit/session/TimedHandlerFactory.java | 132 ++ .../sk89q/worldguard/bukkit/util/Blocks.java | 69 + .../worldguard/bukkit/util/Entities.java | 221 ++ .../sk89q/worldguard/bukkit/util/Events.java | 163 ++ .../worldguard/bukkit/util/HandlerTracer.java | 87 + .../worldguard/bukkit/util/InteropUtils.java | 119 + .../worldguard/bukkit/util/Materials.java | 1708 ++++++++++++++ .../util/logging/ClassSourceValidator.java | 127 ++ .../bukkit/util/report/CancelReport.java | 125 ++ .../bukkit/util/report/PerformanceReport.java | 103 + .../bukkit/util/report/PluginReport.java | 54 + .../bukkit/util/report/SchedulerReport.java | 89 + .../bukkit/util/report/ServerReport.java | 65 + .../bukkit/util/report/ServicesReport.java | 44 + .../bukkit/util/report/WorldReport.java | 80 + .../profile/resolver/PaperProfileService.java | 63 + .../src/main/resources/defaults/blacklist.txt | 62 + .../src/main/resources/defaults/config.yml | 22 + .../main/resources/defaults/config_world.yml | 21 + .../migrations/region/mysql/V1__Initial.sql | 212 ++ .../region/mysql/V2__Bug_fix_and_UUID.sql | 27 + .../migrations/region/sqlite/V1__Initial.sql | 160 ++ .../src/main/resources/plugin.yml | 6 + .../worldguard-core/build.gradle.kts | 20 + .../com/sk89q/worldguard/LocalPlayer.java | 223 ++ .../java/com/sk89q/worldguard/WorldGuard.java | 238 ++ .../sk89q/worldguard/blacklist/Blacklist.java | 267 +++ .../worldguard/blacklist/BlacklistEntry.java | 211 ++ .../blacklist/BlacklistLoggerHandler.java | 83 + .../worldguard/blacklist/MatcherIndex.java | 80 + .../worldguard/blacklist/TrackedEvent.java | 47 + .../worldguard/blacklist/action/Action.java | 28 + .../blacklist/action/ActionResult.java | 30 + .../blacklist/action/ActionType.java | 81 + .../blacklist/action/AllowAction.java | 44 + .../blacklist/action/BanAction.java | 55 + .../blacklist/action/DenyAction.java | 44 + .../blacklist/action/KickAction.java | 55 + .../blacklist/action/LogAction.java | 51 + .../blacklist/action/NotifyAction.java | 51 + .../blacklist/action/RepeatGuardedAction.java | 37 + .../blacklist/action/TellAction.java | 56 + .../event/AbstractBlacklistEvent.java | 77 + .../blacklist/event/BlacklistEvent.java | 87 + .../blacklist/event/BlockBlacklistEvent.java | 51 + .../event/BlockBreakBlacklistEvent.java | 51 + .../event/BlockDispenseBlacklistEvent.java | 56 + .../event/BlockInteractBlacklistEvent.java | 51 + .../event/BlockPlaceBlacklistEvent.java | 51 + .../worldguard/blacklist/event/EventType.java | 50 + .../event/ItemAcquireBlacklistEvent.java | 51 + .../blacklist/event/ItemBlacklistEvent.java | 51 + .../event/ItemDestroyWithBlacklistEvent.java | 57 + .../event/ItemDropBlacklistEvent.java | 51 + .../event/ItemEquipBlacklistEvent.java | 51 + .../event/ItemUseBlacklistEvent.java | 51 + .../blacklist/logger/ConsoleHandler.java | 47 + .../blacklist/logger/DatabaseHandler.java | 126 ++ .../blacklist/logger/FileHandler.java | 245 ++ .../blacklist/logger/LogFileWriter.java | 86 + .../blacklist/logger/LoggerHandler.java | 42 + .../worldguard/chest/ChestProtection.java | 74 + .../worldguard/chest/SignChestProtection.java | 117 + .../worldguard/commands/CommandUtils.java | 136 ++ .../commands/DebuggingCommands.java | 71 + .../worldguard/commands/GeneralCommands.java | 241 ++ .../commands/ProtectionCommands.java | 45 + .../worldguard/commands/ToggleCommands.java | 182 ++ .../commands/WorldGuardCommands.java | 309 +++ .../commands/region/FlagHelperBox.java | 500 +++++ .../commands/region/MemberCommands.java | 232 ++ .../commands/region/RegionCommands.java | 1216 ++++++++++ .../commands/region/RegionCommandsBase.java | 429 ++++ .../region/RegionPrintoutBuilder.java | 391 ++++ .../worldguard/commands/task/RegionAdder.java | 104 + .../commands/task/RegionLister.java | 282 +++ .../commands/task/RegionManagerLoader.java | 53 + .../commands/task/RegionManagerSaver.java | 53 + .../commands/task/RegionRemover.java | 93 + .../config/ConfigurationManager.java | 173 ++ .../worldguard/config/WorldConfiguration.java | 264 +++ .../config/YamlConfigurationManager.java | 125 ++ .../config/YamlWorldConfiguration.java | 125 ++ .../sk89q/worldguard/domains/Association.java | 31 + .../worldguard/domains/DefaultDomain.java | 458 ++++ .../com/sk89q/worldguard/domains/Domain.java | 73 + .../sk89q/worldguard/domains/GroupDomain.java | 151 ++ .../worldguard/domains/PlayerDomain.java | 216 ++ .../worldguard/internal/PermissionModel.java | 24 + .../permission/AbstractPermissionModel.java | 44 + .../permission/RegionPermissionModel.java | 199 ++ .../internal/platform/DebugHandler.java | 35 + .../internal/platform/StringMatcher.java | 174 ++ .../internal/platform/WorldGuardPlatform.java | 172 ++ .../internal/util/sql/StatementUtils.java | 59 + .../protection/AbstractRegionSet.java | 52 + .../protection/ApplicableRegionSet.java | 215 ++ .../DelayedRegionOverlapAssociation.java | 47 + .../protection/FailedLoadRegionSet.java | 129 ++ .../protection/FlagValueCalculator.java | 582 +++++ .../protection/PermissiveRegionSet.java | 121 + .../protection/RegionResultSet.java | 186 ++ .../AbstractRegionOverlapAssociation.java | 127 ++ .../protection/association/Associables.java | 58 + .../association/ConstantAssociation.java | 40 + .../DelayedRegionOverlapAssociation.java | 79 + .../association/RegionAssociable.java | 40 + .../association/RegionOverlapAssociation.java | 53 + .../protection/flags/BooleanFlag.java | 66 + .../protection/flags/BuildFlag.java | 52 + .../protection/flags/CommandStringFlag.java | 59 + .../protection/flags/DoubleFlag.java | 55 + .../protection/flags/EntityTypeFlag.java | 62 + .../worldguard/protection/flags/EnumFlag.java | 101 + .../worldguard/protection/flags/Flag.java | 224 ++ .../protection/flags/FlagContext.java | 172 ++ .../worldguard/protection/flags/FlagUtil.java | 64 + .../worldguard/protection/flags/Flags.java | 278 +++ .../protection/flags/GameModeTypeFlag.java | 62 + .../protection/flags/IntegerFlag.java | 55 + .../protection/flags/InvalidFlagFormat.java | 29 + .../protection/flags/LazyLocation.java | 101 + .../protection/flags/LocationFlag.java | 156 ++ .../worldguard/protection/flags/MapFlag.java | 129 ++ .../protection/flags/NumberFlag.java | 59 + .../protection/flags/RegionGroup.java | 59 + .../protection/flags/RegionGroupFlag.java | 96 + .../protection/flags/RegistryFlag.java | 66 + .../worldguard/protection/flags/SetFlag.java | 106 + .../protection/flags/StateFlag.java | 206 ++ .../protection/flags/StringFlag.java | 81 + .../protection/flags/TimestampFlag.java | 88 + .../worldguard/protection/flags/UUIDFlag.java | 64 + .../protection/flags/VectorFlag.java | 104 + .../protection/flags/WeatherTypeFlag.java | 62 + .../flags/registry/FlagConflictException.java | 28 + .../flags/registry/FlagRegistry.java | 93 + .../flags/registry/SimpleFlagRegistry.java | 181 ++ .../flags/registry/UnknownFlag.java | 49 + .../managers/RegionContainerImpl.java | 298 +++ .../protection/managers/RegionDifference.java | 83 + .../protection/managers/RegionManager.java | 441 ++++ .../protection/managers/RemovalStrategy.java | 40 + .../managers/index/AbstractRegionIndex.java | 27 + .../managers/index/ChunkHashTable.java | 381 ++++ .../managers/index/ConcurrentRegionIndex.java | 32 + .../managers/index/HashMapIndex.java | 314 +++ .../managers/index/PriorityRTreeIndex.java | 113 + .../managers/index/RegionIndex.java | 178 ++ .../managers/migration/AbstractMigration.java | 82 + .../managers/migration/DriverMigration.java | 89 + .../managers/migration/Migration.java | 34 + .../migration/MigrationException.java | 43 + .../managers/migration/UUIDMigration.java | 246 ++ .../storage/DifferenceSaveException.java | 41 + .../managers/storage/DriverType.java | 30 + .../storage/MemoryRegionDatabase.java | 60 + .../managers/storage/RegionDatabase.java | 81 + .../managers/storage/RegionDatabaseUtils.java | 69 + .../managers/storage/RegionDriver.java | 61 + .../managers/storage/StorageException.java | 42 + .../storage/file/DirectoryYamlDriver.java | 98 + .../managers/storage/file/YamlRegionFile.java | 349 +++ .../managers/storage/sql/DataLoader.java | 338 +++ .../managers/storage/sql/DataUpdater.java | 168 ++ .../storage/sql/DomainTableCache.java | 53 + .../managers/storage/sql/RegionInserter.java | 189 ++ .../managers/storage/sql/RegionRemover.java | 88 + .../managers/storage/sql/RegionUpdater.java | 341 +++ .../managers/storage/sql/SQLDriver.java | 257 +++ .../storage/sql/SQLRegionDatabase.java | 277 +++ .../managers/storage/sql/StatementBatch.java | 54 + .../managers/storage/sql/TableCache.java | 262 +++ .../regions/GlobalProtectedRegion.java | 103 + .../regions/ProtectedCuboidRegion.java | 181 ++ .../regions/ProtectedPolygonalRegion.java | 191 ++ .../protection/regions/ProtectedRegion.java | 753 +++++++ .../regions/ProtectedRegionMBRConverter.java | 56 + .../protection/regions/QueryCache.java | 115 + .../protection/regions/RegionContainer.java | 223 ++ .../protection/regions/RegionQuery.java | 643 ++++++ .../protection/regions/RegionType.java | 52 + .../protection/util/DomainInputResolver.java | 180 ++ .../protection/util/NormativeOrders.java | 144 ++ .../util/RegionCollectionConsumer.java | 70 + .../util/UnresolvedNamesException.java | 41 + .../util/WorldEditRegionConverter.java | 75 + .../session/AbstractSessionManager.java | 217 ++ .../sk89q/worldguard/session/MoveType.java | 54 + .../com/sk89q/worldguard/session/Session.java | 246 ++ .../worldguard/session/SessionManager.java | 121 + .../worldguard/session/WorldPlayerTuple.java | 63 + .../worldguard/session/handler/EntryFlag.java | 71 + .../worldguard/session/handler/ExitFlag.java | 113 + .../session/handler/FarewellFlag.java | 97 + .../worldguard/session/handler/FeedFlag.java | 89 + .../handler/FlagValueChangeHandler.java | 77 + .../session/handler/GameModeFlag.java | 93 + .../worldguard/session/handler/GodMode.java | 83 + .../session/handler/GreetingFlag.java | 93 + .../worldguard/session/handler/Handler.java | 148 ++ .../worldguard/session/handler/HealFlag.java | 91 + .../session/handler/InvincibilityFlag.java | 76 + .../session/handler/NotifyEntryFlag.java | 73 + .../session/handler/NotifyExitFlag.java | 62 + .../session/handler/TimeLockFlag.java | 87 + .../session/handler/WaterBreathing.java | 59 + .../session/handler/WeatherLockFlag.java | 74 + .../sk89q/worldguard/util/ChangeTracked.java | 42 + .../com/sk89q/worldguard/util/Entities.java | 48 + .../java/com/sk89q/worldguard/util/Enums.java | 81 + .../com/sk89q/worldguard/util/Locations.java | 40 + .../com/sk89q/worldguard/util/MathUtils.java | 67 + .../sk89q/worldguard/util/MessagingUtil.java | 52 + .../com/sk89q/worldguard/util/Normal.java | 119 + .../com/sk89q/worldguard/util/SpongeUtil.java | 205 ++ .../util/WorldGuardExceptionConverter.java | 98 + .../worldguard/util/collect/EntryBase.java | 30 + .../util/collect/LongBaseHashTable.java | 130 ++ .../worldguard/util/collect/LongHash.java | 48 + .../worldguard/util/collect/LongHashSet.java | 199 ++ .../util/collect/LongHashTable.java | 65 + .../util/command/CommandFilter.java | 207 ++ .../util/concurrent/EvenMoreExecutors.java | 73 + .../formatting/component/BlacklistNotify.java | 32 + .../util/formatting/component/Notify.java | 33 + .../com/sk89q/worldguard/util/io/Closer.java | 308 +++ .../util/logging/LoggerToChatHandler.java | 77 + .../util/logging/RecordMessagePrefixer.java | 77 + .../util/profiler/SamplerBuilder.java | 157 ++ .../worldguard/util/profiler/StackNode.java | 121 + .../util/profiler/StackTraceNode.java | 46 + .../util/profiler/ThreadIdFilter.java | 37 + .../util/profiler/ThreadNameFilter.java | 41 + .../util/report/ApplicableRegionsReport.java | 54 + .../worldguard/util/report/ConfigReport.java | 81 + .../worldguard/util/report/RegionReport.java | 42 + .../worldguard/util/sql/DataSourceConfig.java | 144 ++ .../java/com/sk89q/worldguard/TestPlayer.java | 283 +++ .../worldguard/domains/DefaultDomainTest.java | 116 + .../protection/ApplicableRegionSetTest.java | 650 ++++++ .../FlagValueCalculatorMapFlagTest.java | 123 + .../protection/FlagValueCalculatorTest.java | 1970 +++++++++++++++++ .../protection/HashMapIndexPriorityTest.java | 33 + .../HashMapIndexRegionOverlapTest.java | 33 + .../protection/HashMapIndexRemovalTest.java | 111 + .../protection/HashMapIndexTest.java | 33 + .../protection/MockApplicableRegionSet.java | 99 + .../protection/PriorityRTreeIndexTest.java | 33 + .../PriorityRTreeRegionEntryExitTest.java | 33 + .../PriorityRTreeRegionOverlapTest.java | 33 + .../PriorityRTreeRegionPriorityTest.java | 33 + .../protection/RegionEntryExitTest.java | 152 ++ .../protection/RegionOverlapTest.java | 257 +++ .../protection/RegionPriorityTest.java | 168 ++ .../regions/RegionIntersectTest.java | 125 ++ .../worldguard/util/CommandFilterTest.java | 196 ++ .../worldguard-libs/build.gradle.kts | 3 + .../worldguard-libs/bukkit/build.gradle.kts | 1 + .../worldguard-libs/core/build.gradle.kts | 18 + sk89q-worldguard/worldguard-logo.png | Bin 0 -> 10220 bytes sk89q-worldguard/worldguard-logo.svg | 1 + 363 files changed, 50079 insertions(+) create mode 100644 sk89q-worldguard/.gitattributes create mode 100644 sk89q-worldguard/.github/FUNDING.yml create mode 100644 sk89q-worldguard/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 sk89q-worldguard/.github/ISSUE_TEMPLATE/config.yml create mode 100644 sk89q-worldguard/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 sk89q-worldguard/.gitignore create mode 100644 sk89q-worldguard/.travis.yml create mode 100644 sk89q-worldguard/CHANGELOG.md create mode 100644 sk89q-worldguard/CONTRIBUTING.md create mode 100644 sk89q-worldguard/HEADER.txt create mode 100644 sk89q-worldguard/LICENSE.txt create mode 100644 sk89q-worldguard/README.md create mode 100644 sk89q-worldguard/build.gradle.kts create mode 100644 sk89q-worldguard/buildSrc/build.gradle.kts create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/ArtifactoryConfig.kt create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/CommonConfig.kt create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/GradleExtras.kt create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/LibsConfig.kt create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/PlatformConfig.kt create mode 100644 sk89q-worldguard/buildSrc/src/main/kotlin/Versions.kt create mode 100644 sk89q-worldguard/config/checkstyle/checkstyle.xml create mode 100644 sk89q-worldguard/config/checkstyle/import-control.xml create mode 100644 sk89q-worldguard/config/checkstyle/suppressions.xml create mode 100644 sk89q-worldguard/gradle.properties create mode 100644 sk89q-worldguard/gradle/wrapper/gradle-wrapper.jar create mode 100644 sk89q-worldguard/gradle/wrapper/gradle-wrapper.properties create mode 100755 sk89q-worldguard/gradlew create mode 100644 sk89q-worldguard/gradlew.bat create mode 100644 sk89q-worldguard/settings.gradle.kts create mode 100644 sk89q-worldguard/worldguard-bukkit/build.gradle.kts create mode 100644 sk89q-worldguard/worldguard-bukkit/contrib/blacklist_table.sql create mode 100644 sk89q-worldguard/worldguard-bukkit/contrib/region_manual_update_20110325.sql create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitConfigurationManager.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitDebugHandler.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitOfflinePlayer.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitRegionContainer.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitStringMatcher.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitUtil.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldConfiguration.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/ProtectionQuery.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/chest/BukkitSignChestProtection.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/BulkEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvents.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/Handleable.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/AbstractBlockEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/BreakBlockEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/PlaceBlockEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/UseBlockEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelAttempt.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogger.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogging.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockBreakEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockPlaceEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingEntityDamageByEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingPlayerInteractEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/AbstractEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DamageEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DestroyEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/SpawnEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/UseEntityEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/inventory/UseItemEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/player/ProcessPlayerEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/TargetMatcherSet.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/WGMetadata.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/AbstractListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlacklistListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlockedPotionsListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BuildPermissionListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/ChestProtectionListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/DebuggingListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/InvincibilityListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerModesListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerMoveListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionFlagsListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardBlockListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCommandBookListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardServerListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardVehicleListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWeatherListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWorldListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldRulesListener.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonExtendKey.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonRetractKey.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/EventDebounce.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/AbstractEventDebounce.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/BlockEntityEventDebounce.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/EntityEntityEventDebounce.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/InventoryMoveItemEventDebounce.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/DisallowedPVPEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/flags/FlagContextCreateEvent.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/BukkitSessionManager.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/TimedHandlerFactory.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Blocks.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Entities.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Events.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/HandlerTracer.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/InteropUtils.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Materials.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/logging/ClassSourceValidator.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/CancelReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PerformanceReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PluginReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/SchedulerReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServerReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServicesReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/WorldReport.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/util/profile/resolver/PaperProfileService.java create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/blacklist.txt create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config.yml create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config_world.yml create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V1__Initial.sql create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/sqlite/V1__Initial.sql create mode 100644 sk89q-worldguard/worldguard-bukkit/src/main/resources/plugin.yml create mode 100644 sk89q-worldguard/worldguard-core/build.gradle.kts create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/WorldGuard.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/Blacklist.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistEntry.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistLoggerHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/MatcherIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/TrackedEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/Action.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionResult.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionType.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/AllowAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/BanAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/DenyAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/KickAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/LogAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/NotifyAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/RepeatGuardedAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/TellAction.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/AbstractBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBreakBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockDispenseBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockInteractBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockPlaceBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/EventType.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemAcquireBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDestroyWithBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDropBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemEquipBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemUseBlacklistEvent.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/ConsoleHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/DatabaseHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LogFileWriter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LoggerHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/ChestProtection.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/SignChestProtection.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/CommandUtils.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/DebuggingCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/GeneralCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ProtectionCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ToggleCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/WorldGuardCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/FlagHelperBox.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionAdder.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionLister.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerLoader.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerSaver.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionRemover.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/WorldConfiguration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlWorldConfiguration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Association.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Domain.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/PermissionModel.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/AbstractPermissionModel.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/DebugHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/StringMatcher.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/util/sql/StatementUtils.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/AbstractRegionSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/DelayedRegionOverlapAssociation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FailedLoadRegionSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/PermissiveRegionSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/RegionResultSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/AbstractRegionOverlapAssociation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/Associables.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/DelayedRegionOverlapAssociation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BooleanFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BuildFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/CommandStringFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/DoubleFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EntityTypeFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagContext.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagUtil.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flags.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/GameModeTypeFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/IntegerFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/InvalidFlagFormat.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LazyLocation.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LocationFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/MapFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/NumberFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegistryFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StateFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StringFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/TimestampFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/UUIDFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/VectorFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/WeatherTypeFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagConflictException.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagRegistry.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/SimpleFlagRegistry.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/UnknownFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionContainerImpl.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/DriverMigration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/Migration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/MigrationException.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/UUIDMigration.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DriverType.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionDatabase.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabase.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabaseUtils.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDriver.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/StorageException.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DirectoryYamlDriver.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLDriver.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionDatabase.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegionMBRConverter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/QueryCache.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionContainer.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionQuery.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/NormativeOrders.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/WorldEditRegionConverter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/AbstractSessionManager.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/MoveType.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/Session.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/SessionManager.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/WorldPlayerTuple.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/EntryFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/ExitFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FarewellFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FeedFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FlagValueChangeHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GameModeFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GodMode.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GreetingFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/Handler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/HealFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/InvincibilityFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyEntryFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyExitFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/TimeLockFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WaterBreathing.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WeatherLockFlag.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Entities.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Enums.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Locations.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MathUtils.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MessagingUtil.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Normal.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/SpongeUtil.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/WorldGuardExceptionConverter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/command/CommandFilter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/concurrent/EvenMoreExecutors.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/BlacklistNotify.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/Notify.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/io/Closer.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/LoggerToChatHandler.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/RecordMessagePrefixer.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/SamplerBuilder.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackNode.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackTraceNode.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadIdFilter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadNameFilter.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ApplicableRegionsReport.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ConfigReport.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/RegionReport.java create mode 100644 sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/domains/DefaultDomainTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/ApplicableRegionSetTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorMapFlagTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRemovalTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionOverlapTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionPriorityTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/regions/RegionIntersectTest.java create mode 100644 sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/util/CommandFilterTest.java create mode 100644 sk89q-worldguard/worldguard-libs/build.gradle.kts create mode 100644 sk89q-worldguard/worldguard-libs/bukkit/build.gradle.kts create mode 100644 sk89q-worldguard/worldguard-libs/core/build.gradle.kts create mode 100644 sk89q-worldguard/worldguard-logo.png create mode 100644 sk89q-worldguard/worldguard-logo.svg diff --git a/sk89q-worldguard/.gitattributes b/sk89q-worldguard/.gitattributes new file mode 100644 index 000000000..cc8847fd1 --- /dev/null +++ b/sk89q-worldguard/.gitattributes @@ -0,0 +1,2 @@ +*.java diff=java + diff --git a/sk89q-worldguard/.github/FUNDING.yml b/sk89q-worldguard/.github/FUNDING.yml new file mode 100644 index 000000000..e1373c3a7 --- /dev/null +++ b/sk89q-worldguard/.github/FUNDING.yml @@ -0,0 +1 @@ +github: enginehub diff --git a/sk89q-worldguard/.github/ISSUE_TEMPLATE/bug_report.md b/sk89q-worldguard/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..a97854a85 --- /dev/null +++ b/sk89q-worldguard/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Report a way in which WorldGuard is not working as intended +title: '' +labels: type:bug, status:pending +assignees: '' + +--- + +**Versions** + +WorldEdit version: + +WorldGuard version: + +Platform version: + + +**Describe the bug** + + +**To Reproduce** + +1. ... +2. ... + +**Expected behavior** + + +**Screenshots** + + +**Additional context** + diff --git a/sk89q-worldguard/.github/ISSUE_TEMPLATE/config.yml b/sk89q-worldguard/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..b40fa58a4 --- /dev/null +++ b/sk89q-worldguard/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: EngineHub Discord + url: https://discord.gg/EngineHub + about: Please ask and answer questions here. diff --git a/sk89q-worldguard/.github/ISSUE_TEMPLATE/feature_request.md b/sk89q-worldguard/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..cc2991075 --- /dev/null +++ b/sk89q-worldguard/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for WorldGuard +title: '' +labels: type:feature-request, status:pending +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + diff --git a/sk89q-worldguard/.gitignore b/sk89q-worldguard/.gitignore new file mode 100644 index 000000000..c2ab782df --- /dev/null +++ b/sk89q-worldguard/.gitignore @@ -0,0 +1,19 @@ +.classpath +.project +.settings/ +target/ + +.DS_Store + +**/*.iml +**/.idea + +bin/ +dependency-reduced-pom.xml +*-private.sh + +.gradle/ +**/build/ +out/ + +scripts/ diff --git a/sk89q-worldguard/.travis.yml b/sk89q-worldguard/.travis.yml new file mode 100644 index 000000000..2cbac18ad --- /dev/null +++ b/sk89q-worldguard/.travis.yml @@ -0,0 +1,8 @@ +language: java +dist: trusty +jdk: + - oraclejdk11 +notifications: + email: false +before_install: chmod +x gradlew +script: ./gradlew build -S diff --git a/sk89q-worldguard/CHANGELOG.md b/sk89q-worldguard/CHANGELOG.md new file mode 100644 index 000000000..628586db4 --- /dev/null +++ b/sk89q-worldguard/CHANGELOG.md @@ -0,0 +1,998 @@ +# Changelog + +## 7.0.6 beta 1 + +* Add support for 1.17 blocks/items, drop support for 1.16. +* Add use-dripleaf flag for "toggling" big dripleaf blocks. +* Fix support for fully negative-height regions. +* Fix crop-growth config option. +* Be more rigorous with protecting blocks against dispenser behaviors. + +## 7.0.5 + +* Add a use-anvil flag and exclude it from the use flag (since they can break on use.) +* Expand crop-related options (crop-growth, etc) to sweet berry bushes, nether wart, and bamboo. +* Add a config option (`mobs.block-vehicle-entry`) to prevent non-players from entering vehicles. +* Add a UUID flag (for developers/API usage). +* Add map query methods with fallback flag (for developers/API usage). +* Add `on` and `off` optional arguments for `/rg toggle-bypass` command. +* Add additional timings info for session handlers. +* Fix sponge-simulation clearing NBT from blocks. Note that if you are using sponge simulation you should switch to something like CraftBook as the feature will be removed from WorldGuard in a future (major) version. +* Fix the `/rg` command showing up as unknown client-side to players without bypass perms. +* Fix error propagation from third-party flag loading causing WG to error. +* Fix a (harmless) exception that occurred when swapping armor slots to the offhand slot. +* Fix empty lines being sent on enderpearl/chorus-fruit teleport if the deny messages were empty. +* Fix an issue with falling blocks when using max-priority-association. +* Fix performance issues with third-party plugins querying protection for offline players. +* Fix dispensing shulkers over region boundaries. +* Fix iron door interaction being denied as if it were a regular door. +* Fix being able to enter/exit regions with the flag denied by using entity mounting functions in other plugins. + +## 7.0.4 (including beta1) + +* Add support for MC 1.16. Dropped support for previous versions. +* Add respawn-anchors flag mirroring the sleep flag (e.g. to prevent using respawn anchors to cause explosions in the overworld) +* Add nether vines to vine-growth flag. +* Add config option to disable the bypass permission cache. +* Fix water-flow flags not checking waterlogged blocks. +* Add config option to consider non-player causes (e.g. pistons, flowing water) to only be members of the highest priority region. This should be enabled for plots-within-city-like setups to prevent things in plots from modifying the city. +* Fix items dropping from falling blocks that were suppressed by other plugins. +* Add config option to block turtle egg trampling. +* Fix ride flag being checked on striders that didn't have saddles. +* Add config option to have /rg bypass toggled off on login. +* Developer changes: Methods taking names in Domains are now explicitly deprecated (was previously just a javadoc comment.); These will eventually be removed: there is no reason to use names over UUIDs. Offline players and NPCs alike have stable UUIDs. +* Also for developers; The new `com.sk89q.worldguard.protection.util.WorldEditRegionConverter` class has some useful static methods to convert to and from WorldEdit's Region and WorldGuard's ProtectedRegion classes. + + +## 7.0.3 + +* This is the last release supporting MC 1.14 and 1.15. +* Decouple chest-access flag from interact flag. IMPORTANT: If you relied on allowing players to access chests via setting + the interact flag to allow, you will now need to set the chest-access flag to allow instead (or as well). This does not + affect any setups where users were *not* supposed to be able to access chests and other inventories. +* Add natural-health-regen and natural-hunger-drain flags. Unlike the heal/feed flags, these can not restore or deplete health/hunger. +* Fix the interact flag allowing breaking of turtle eggs (now tied directly to block-trampling flag). +* Fix a memory leak that could occur with many many explosives. +* Add regions.titles-always-use-default-times config option. Set this to true if you use greeting/farewell titles and another plugin + makes the titles disappear too quickly. +* Add protection for cauldrons water level changing. +* Improve diagnostic hints and information, such as spawn protection warnings, flag colors in region info, etc. +* Fix waterlogged blocks bypassing water-flow flag. +* Workaround for a CraftBukkit change that made no-physics-sand/gravel non-functional. +* Workaround for CraftBukkit throttling move events. +* Track projectiles shot by dispensers. +* Allowed setting passthrough allow on global to unprotect it if members/owners are added. +* Add support for newer forge clients (FML2) in host keys. +* Allow using WorldEdit's `//world ` command to select a world for region commands. This removes the need to + use `-w ` in every command when running commands from console. +* Add a teleport-message flag, shown when using `/rg tp`. +* Add coral-fade flag, which prevents corals from drying when out of water. +* Fix spammy deny-message when walking over redstone ore with certain flag setups. +* Improve handling of interacting with various tools in hand that didn't modify blocks. (eg opening a chest with an axe in hand) +* Fix allow-all-interact option not being read from per-world configs. +* Fix misattribution of pets (eg wolves) in PvP. +* Improve performance of hopper checks when on recent builds of Paper. +* Add event-handling.break-hoppers-on-denied-move config option to prevent hoppers from being broken (but items still won't be moved). +* Note to developers using the WorldGuard API: If you specifically relied on flags such as greeting/farewell, deny-message, etc being + StringFlags, be warned that this will change in a future version to support JSON text components. Please plan accordingly. + + +## 7.0.2 +* Update to MC 1.15. Still compatible with 1.14, and incompatible with 1.13, as before. +* Add an informational message when defining a region that overlaps vanilla spawn protection. +* Protect against pushing a piston with another piston on a region border. +* Protect against buckets in dispensers on region borders. + +## 7.0.1 +* Add `/rg toggle-bypass` command which temporarily turns off region bypassing until used again. +* More improvements to `/rg flag` and `/rg flags` commands. +* Fix some checks for adding owners to unclaimed regions. +* Fix an issue with time-/weather-lock flags when logging in to a region with the flag set. +* Fix cause tracking in vehicles (incl. breaking lilypads in a boat) +* Use async teleports on Paper. +* Include hopper minecarts in the ignore hoppers config setting. +* Add sugar cane and cacti to the crop-growth flag. Bamboo is also included pending Spigot throwing an event for it. +* Specifically track when players ignite creepers with flint and steel. +* Fix fuzziness when using bonemeal near a protected region. +* Fix color codes not applying in greeting, etc flags when placed after a newline (\n). +* Add applicable region information to `/wg report`. +* Fix pistons flag not applying to sticky pistons pulling blocks. +* Add some custom metrics for bStats. +* Everything in the RC/beta changelog below. + +## 7.0.1 RC 2 (inc. beta 1) +* Due to breaking changes in important Bukkit API, this is not compatible with MC 1.13! +* Update to MC 1.14. Adds protection for new blocks, etc. +* To allow players to read from lecterns, but not take the book, flag interact allow, and then flag chest-access -g nonmembers deny. +* Add ravager-grief flag. +* Fix overprotection of unsaddled pigs with the ride flag. +* Fix lava fire defaulting to off (won't change existing configs). +* Fix damaged/chipped anvils not respecting use flag like regular anvils. +* Add on-equip blacklist event. Note that it is overprotective due to deficiencies in Bukkit API. +* Update WorldEdit CUI when using /rg select. +* Add config option to disable villagers getting zapped into witches. ("weather.disable-villager-witchification") +* Add entity support to interact-whitelist config option (use the Material of the corresponding item). +* Fix vehicle-place protecting the rails at the very top of region boundaries incorrectly. +* Fix for emptying buckets on waterloggable blocks. +* Whitelist all player -> player interactions (previously was spammy in pvp). +* Fix sleep flag not protecting against explosions in nether/end. +* Add config for the teleport-on-void to reset fall distance. +* Fix config options taking material lists requiring "minecraft:" namespace. +* Fix disallowed-lightning-blocks situationally not working. +* Fix errors that occurred when plugins used falling block entities with non-item materials. +* Add special support for Paper servers (papermc.io): Paper's API allows us to track who launched fireworks. +* Fireworks are now protected by default. On Paper servers, members can use them by default (to deal damage), +on non-Paper servers, flag firework-damage allow to revert to old behavior (everyone can deal damage with fireworks by default). +* Misc performance enhancements for Paper servers. +* Add 'regions.use-paper-entity-origin' as an experimental setting (default off) for Paper servers. Enabling it will make non-player entities be treated as members of the place they spawned, instead of their current location when flags are checked. + +## 7.0.0 +* Added support for MC 1.13, dropped support for previous versions. +* Added lots of interactive text in e.g. `/rg info` command. +* Added `/rg flags` command which allows viewing and interactively setting all flags on a region. +* Large behind-the-scenes changes to WorldGuard's API (developers, see https://worldguard.enginehub.org/en/latest/developer/) +* Places that used numeric block ids (e.g. blacklist) should now use Minecraft's new string IDs. +* Fix various interactions with NPCs and region protection. +* Sponge-simulation now destroys kelp and seagrass, and de-waterlogs blocks. +* Added an option to block conduit effects. +* Fix using newlines in string flags such as greeting and farewell. +* Add item-frame-rotation flag. +* Ender chests no longer require chest-access (since they don't have inventories). +* Add kelp to vine-growth flag/config. +* Add block-trampling flag (for turtle eggs and farmland). +* Add snowman-trails flag. +* Track potion area-of-effect cloud causes for pvp etc. protection. +* Add confirmation to /stoplag command. +* Forcefully eject players from vehicles trying to force their way into entry deny regions (and out of exit deny). +* Fix removing child regions when removing a region with children. +* Fix some over-protectiveness of pistons. +* Chat and command flags (send-/receive- and allowed-/blocked-) now respect deny-message flag. +* Misc fixes. + +## 6.2.2 +* Added support for MC 1.12. +* Changed flower pots to fall under the build flag. +* Fix heal flag not functioning. +* Add option to allow Forge clients with host-keys setting. +* Add mobs.block-armor-stand-destroy config. +* Add firework-damage flag. +* Add config option to ignore hopper checks. +* Misc fixes. + +## 6.2 +* Added support for MC 1.11. +* Add a custom flag and session API (for developers, see https://worldguard.enginehub.org/en/latest/developer/) +* Add a config option to ensure location flags (teleport, spawn) are inside their region. +* Misc fixes. + +## 6.1.2 +* Added support for new blocks in 1.8, 1.9, and 1.10. +* Added protection from nether portals being created in protected region (enable via config `nether-portal-protection`). +* Added protection from slime block piston contraptions pushing into or pulling from regions. +* Added `chorus-fruit-teleport` flag, works similar to `enderpearl` to prevent teleportation. +* Added protection from pulling armor stands, items, and entities from protected regions. +* Changed order of greeting and farewell flags on adjacent regions. When leaving a region with a farewell flag and entering one with a greeting flag, the farewell message will be shown first now. +* Changed /heal command to also fill food/saturation meters. +* Changed fall damage prevention to also prevent flying into walls with elytra being fatal. +* Fixed fire-spread flag only preventing destruction, now prevents spreading too as per its name. +* Fixed creepers targeting players being able to blow things up in certain cases. +* Fixed armor stands being removed by /stoplag. +* Fixed some issues with fireballs being able to explode when ghast-fireball was set to deny. +* Fixed a lot of other minor issues. + +## 6.1 + +* Added `exit-via-teleport` flag (default allow) to control exiting an exit=deny region via teleportation. +* Added a `fall-damage` flag to control player damage caused by falling. +* Added a `time-lock` flag to lock players' time of day. Valid values are from 0 to 24000 for absolute time, or +- any number for relative time. +* Added a `weather-lock` flag to lock players' weather. Valid values are "clear" or "downfall". Unset to restore to normal world weather. +* Added `-s` to the event debugging commands to show a stack trace rather than attempt to detect the causing plugin. +* Added support for using the `-e` argument (sets an empty value for the flag) in the flag set command when the type of flag is of the 'set' type. +* Added NPCs from the Citizens plugin to a whitelist so they are not protected. +* Added support for Spigot's BlockExplodeEvent. +* Changed tripwire to fall under the `use` flag. +* Changed enderchests to fall under the `use` flag. +* Changed vehicles and animals so they are not included in the `interact` flag. +* Changed the display of custom blacklist messages to no longer include a period at the end. +* Changed protection logic to consider connected chests. +* Changed the heal and feed flags to increase values for players who are invincible (or in creative mode) but not decrease them. +* Changed PvP protection to consider both attacker and defender locations. +* Fixed missing protection data for some 1.8-added blocks. +* Fixed compatibility issues with MC 1.7. +* Fixed inverted daylight detectors not being protected. +* Fixed spawn eggs not being included in protection. +* Fixed piston blocking not working due to a bug in Spigot. +* Fixed the blocking of certain invalid entity damage events. +* Fixed creeper explosions not being blocked in certain situations with explosion related flags set. +* Fixed "stickiness" with some position-related flags, sometimes resulting in rubber banding in the exit flags. +* Fixed armor stands so that they are treated more like item frames than mobs. +* Fixed blocks (e.g. sugar canes) adjacent to physics blocks (e.g. sand) not updating. +* Fixed a NullPointerException that occurred sometimes when generating the scheduler section of the report function (`/wg report`). +* Fixed the "no XP drops" configuration not functioning following a previous release. +* Fixed changes to region ownership sometimes not triggering a region database save. +* Fixed bucket protection displaying the "deny effect" even if bucket use was not prevented. +* Fixed CommandFilter matching emoticons and other unwanted characters. +* Fixed an exception occurring sometimes during game mode changes. +* Fixed primed TNT being checked twice for TNT flags. + +## 6.0 beta 5 + +MC 1.7.9, 1.7.10 and 1.8 (Spigot only) are supported. + +* Added a feature to detect when another plugin (incorrectly) includes WorldGuard's files, causing errors. +* Added a new `exit-override` flag that can be used on an entry=allow region that players can walk into from an entry=deny region. +* Added a new `entry-deny-message` to change the "entry denied" message. +* Added a new `exit-deny-message` to change the "exit denied" message. +* Changed entry/exit blocking to disembark players from their vehicles. +* Changed the PvP flag to no longer apply to self-attack. +* Fixed [#3220](http://youtrack.sk89q.com/issue/WORLDGUARD-3220): Polygon regions sharing points +* Fixed [#3216](http://youtrack.sk89q.com/issue/WORLDGUARD-3216): Polygon regions 'disappearing' or not working correctly +* Fixed [#3315](http://youtrack.sk89q.com/issue/WORLDGUARD-3315): Invincibility flag and mode not blocking knockback from snowballs, etc. +* Fixed [#3314](http://youtrack.sk89q.com/issue/WORLDGUARD-3314): Protection bypassable by getting a skeleton or creeper to target a player +* Fixed [#3326](http://youtrack.sk89q.com/issue/WORLDGUARD-3326): Enderchests cannot be protected with the use flag +* Fixed [#3327](http://youtrack.sk89q.com/issue/WORLDGUARD-3327): Fake players from some Forge mods are not being detected properly +* Fixed [#3328](http://youtrack.sk89q.com/issue/WORLDGUARD-3328): TypeToken error with CraftBukkit 1.7 in `/wg report` +* Fixed [#3086](http://youtrack.sk89q.com/issue/WORLDGUARD-3086): Region entry / exit messages overlap between seprate worlds +* Fixed [#2542](http://youtrack.sk89q.com/issue/WORLDGUARD-2542): Exit deny regions don't handle respawn / logout consistently +* Fixed [#2731](http://youtrack.sk89q.com/issue/WORLDGUARD-2731): Greeting / farewell messages don't understand region inheritance +* Fixed [#3308](http://youtrack.sk89q.com/issue/WORLDGUARD-3308): Enderchest interaction block not functioning with blacklist.txt +* Fixed [#3312](http://youtrack.sk89q.com/issue/WORLDGUARD-3312): PVP flag blocking enderpearl teleport fall damage +* Fixed [#3309](http://youtrack.sk89q.com/issue/WORLDGUARD-3309): Inventory view commands being blocked in protected regions +* Fixed [#3310](http://youtrack.sk89q.com/issue/WORLDGUARD-3310): USE flag is not allowing workbench usage +* Fixed [#3330](http://youtrack.sk89q.com/issue/WORLDGUARD-3330): Build permissions preventing block placement +* API: Added `BukkitUtil.toRegion(Chunk)` + +## 6.0 beta 4 + +MC 1.7.9, 1.7.10 and 1.8 (Spigot only) are supported. + +* Added better support for MC 1.8 blocks (regarding the use flag, etc.). +* Added an an experimental "lite" version of WarmRoast to CPU profile your server (try it: `/wg profile -p`) +* Added permissions for individual flag values (i.e. `worldguard.region.flag.flags.use.allow`). +* Added support for slightly older versions of Bukkit 1.7.9. +* Improved `/wg report`'s output (try it: `/wg report -p`) +* Fixed armor stands not being protected properly. +* Fixed lack of wood plate support with the `USE` flag. +* Fixed left click also being blocked when only right click needed to be blocked. +* Fixed items and paintings/item frames not decaying under some versions of Spigot. +* Fixed parent-child inheritance so the child will always inherit the parent. +* Fixed inheritance issues with a flag is set with a region group on a parent region. +* Fixed high CPU usage when Citizens is also installed. +* Fixed `on-acquire` in the blacklist so it also handles item transfers between inventories. +* Fixed the blacklist not supporting color codes. +* Changed the PvP flag so it applies to self-attack like in WG 5 (this change *may* be reverted). + +## 6.0 beta 3 + +MC 1.7.10 and 1.8 (Spigot only) are supported. + +* Support both MC 1.7.10 and 1.8. +* Don't apply the region override permission to PvP. +* Fix incorrect detection of hostile and ambient creatures when protecting against PvE. +* Fix the splash potion flag not working with the other flags. +* Fix potion blocking. +* Fix inability to blacklist the enchantment table and the workbench. +* Make PASSTHROUGH=DENY useful on ``__global__``. +* Fix issues (pistons broken, etc.) caused by adding members to ``__global__``. +* Make the EXP_DROPS flag a build-compat flag and set its deny message. +* Give the player back his/her XP bottle if it was blocked. +* Expand the interaction whitelist configuration to "physical" events. +* Catch command prefixes in the command flags. +* Add a RIDE flag for vehicles. +* Check for names in addition to UUIDs with /rg list. +* Whitelist hoppers and let them cross regions regardless of region protection. +* Revert USE flag's functionality to 5.x, add new INTERACT flag. +* Fix spawn egg blocking. +* Fix failed detection of incorrect blacklist block and item names. +* Fix command prefix bypass with the op command blocking configuration. +* Rewrite and update report generation (wg report). +* Add event cancellation debug commands. +* Fix location flags causing NullPointerExceptions. +* Show more details in error log which world the error belongs to. +* Add missing color codes when parsing input. +* Let players use NPCs always. +* Fix [WorldGuard] in log messages stacking on reloads. +* Better handle empty group / player names due to bad region data. +* Fix players being invincible with BUILD=deny. +* Fix certain entities not being damaged in protected regions. +* Improve entity detection in Entities utility class, add Entities.isNonPlayerCreature(). +* Add missing SQL table prefix in statement selecting world names. + +## 6.0 beta 2 + +* Added an `-e` parameter to `/rg flag` that sets the flag to an empty value. You can use this to, for example, make it so the `deny-message` flag is empty, meaning that players won't get a message at all. +* Added a visual smoke effect when an action on a block has been denied. +* Added `event-handling.interaction-whitelist`, a list of items or blocks that should never be protected. For example, adding `wooden_door` to the list would make it so that doors could be used by anyone regardless if they have permission or not. You may still protect the blocks via other means. +* Added `event-handling.emit-block-use-at-feet`, a list of items that, when used, will also be considered as the player using that item on the block at the player's feet. The purpose of this setting is, for example, to allow you to (sort of) prevent the use of a mod-added item that does not emit events. +* Added `-s` to silently turn toggle `/stoplag` and `-i` to show the current state of the setting. (Thanks, [stuntguy3000](https://github.com/stuntguy3000)). +* Added support for Bukkit [Material](http://jd.bukkit.org/rb/apidocs/org/bukkit/Material.html) names in the blacklist. +* Fixed [#3167](http://youtrack.sk89q.com/issue/WORLDGUARD-3167): Saplings can be used without permission although it won't place blocks inside the region +* Fixed [#3168](http://youtrack.sk89q.com/issue/WORLDGUARD-3168): Grown saplings that are partially prevented from placing all of its leaves may not remove the sapling +* Fixed [#3169](http://youtrack.sk89q.com/issue/WORLDGUARD-3169): Bonemeal usage falls under the USE flag when it should really fall under building +* Fixed [#3174](http://youtrack.sk89q.com/issue/WORLDGUARD-3174): Boat placement is not properly prevented in a protected region +* Fixed [#3130](http://youtrack.sk89q.com/issue/WORLDGUARD-3130): Polygonal selections allow unlimited claim volumes with /rg claim +* Fixed [#3137](http://youtrack.sk89q.com/issue/WORLDGUARD-3137): Claiming infinite regions is possible due to integer overflow +* Fixed [#3171](http://youtrack.sk89q.com/issue/WORLDGUARD-3171): Can't pickup XP in protected regions without permission +* Fixed [#3170](http://youtrack.sk89q.com/issue/WORLDGUARD-3170): Boats without a driver can be used to break lily pads +* Fixed [#3172](http://youtrack.sk89q.com/issue/WORLDGUARD-3172): Players cannot be damaged by mobs in protected regions unless they have permission +* Fixed [#3152](http://youtrack.sk89q.com/issue/WORLDGUARD-3152): BUILD overrides all other flags, unlike in WG 5 +* Fixed [#3154](http://youtrack.sk89q.com/issue/WORLDGUARD-3154): notify-enterflag doesn't work with horses and other vehicles +* Fixed [#3166](http://youtrack.sk89q.com/issue/WORLDGUARD-3166): Trees do not grow naturally within protected regions + +## 6.0 beta 1 + +### UUID support + +Regions now fully support the use of UUIDs rather than names for storing the owners and members of a region. Commands still accept names, but they are translated into UUIDs in the background. For users who already have existing region data, names will be automatically converted to UUIDs on the first run of WorldGuard. + +It is still possible to specify names rather than a UUID by using the `-n` flag with the region membership commands. + +### 'Deny by default' + +Previously, a pre-determined list of blocks and entities was used to determine whether an action by the player should be blocked. However, this has been reversed so that every action on a block or entity is denied (in a protected region) unless it has been deemed safe (such as, for example, right clicking with steak). Mods are now supported as a result. + +The `USE` flag has been repurposed as a general "interaction" flag and covers every left click or right click of a block or entity. It also covers interaction that falls outside clicking. + +### Exhaustive protection + +More events are now handled in the protection code. For example, + +* It is no longer possible to drop sand or gravel into a protected region from outside. +* It is no longer possible to grief a region by growing a tree into it. +* The entry and exit flags now handle players sitting on vehicles properly. +* Piston-moved blocks cannot be pushed into a different protected region. +* Owners of tamable animals are now considered in protection code. + +Even water and lava flow can also be checked, although this requires that it be explicitly enabled in the configuration (flow events are very frequent, so they incur extra CPU cost). + +The goal is to provide extremely exhaustive protection. + +### Large performance boost for spatial queries + +The performance of spatial queries for regions has been substantially increased at least an order of magnitude. Spatial queries are a necessity for the region code to query the list of regions that apply to a location in the world. There should be a negligible difference between 4, 40, and 4000 regions as long as too many regions are not overlapping. + +### Background region operations + +Region commands are now executed in the background. Notably, saves and loads will not lock up your server while they complete. Additionally, changes to regions are now saved periodically rather than immediately after every change. + +### Updated MySQL region support + +MySQL support has been completely overhauled. It should now be faster, no longer crash on foreign key index errors, use transactions, and support automatic creation and migration of tables (including support for table prefixes). + +### Partial region data saving + +For users of MySQL, WorldGuard now only saves changed and deleted regions when saving region data. No longer does WorldGuard have to rewrite the entire region database on each save. + +### Improved handling of related flags + +Multiple flags that apply to an event are now evaluated together if they are similar. For example, if a player right clicks a bed to sleep in it, both the `USE` and `SLEEP` flag are checked since they are both interaction-related. If one of them is `DENY`, then sleeping is denied (remember, `DENY` overrides `ALLOW`). If one of them is `ALLOW` and the other is not `DENY`, then sleeping is permitted. + +Only one "category" of flags needs to evaluate to true to permit an action. `DENY` will not cross categories. For example, if `BUILD` is deny, it will not override `SLEEP`, so if `SLEEP` is set to `ALLOW`, sleeping will be permitted. This is fairly similar to how it worked on WorldGuard 5. + +### Flag groups now work properly + +While you could always set a group to a flag's value, the functionality was incomplete and did not work most of the time. + +Now that groups are supported, you can change a flag so it only applies to a certain group of players. For example, `/rg flag example sleep deny -g members` would deny sleeping for only members of the `example` region. + +### Inheritance now works as expected + +Previously, a region only inherited flags and other details from its parents if the parent and the child overlapped at the location queried. This is no longer the case and inheritance is now always applied. + +In addition, a new `-g` flag for `/rg define` lets you create a region with no physical area so that you can assign flags and use this region as a "template region." + +### Settable deny message + +It is now possible to change the message that users get when they are prevented from interacting with blocks or entities. This message is defined as a region flag, so you can set it on the `__global__` region or override it in a specific region. In addition, the tone and color of the default message has been softened, but you are free to change it entirely. + +### Blacklist support for data values (and later, variants) + +The blacklist was rewritten to support more than just block and item IDs. The immediate result is that you can now use data values, but as those are in the process of being deprecated by Mojang, it also allows support for Minecraft's new block variants in the future. + +### Debounced events + +Events that tend to reoccur very frequently (such as the ones that occur when you stand on a pressure plate continuously) have been "debounced" to reduce the number of checks that WorldGuard has to perform, reducing the amount of wasted CPU. In addition, the "you don't have permission" message is rate limited so players are not flooded with the message. + +### New block place and break flags + +A long requested feature was the availability of block place and break flags. These newly added flags work in tandem with the `BUILD` flag but can override `BUILD`. At the moment, it is not possible to allow explicit types of blocks to be placed or broken yet. + +### Build permission nodes + +A new optional (disabled by default in the configuration) feature is the checking of build permission nodes. For every block, entity, and item, the following permission nodes are checked: + +* Block place: `worldguard.build.block.place.` +* Block break: `worldguard.build.block.remove.` +* Block interact: `worldguard.build.block.interact.` +* Entity spawn: `worldguard.build.entity.place.` +* Entity destroy: `worldguard.build.entity.remove.` +* Entity interact: `worldguard.build.entity.interact.` +* Entity damage: `worldguard.build.entity.damage.` +* Item use: `worldguard.build.item.use.` + +In addition, the permissions are also checked in the style of `worldguard.build.block..`, so `worldguard.build.block..place` would work too. + +The list of usable material names comes from the [Material enumeration in Bukkit](http://jd.bukkit.org/rb/apidocs/org/bukkit/Material.html). For example, the permission for placing the bed block would be `worldguard.build.build.place.bed_block`. Be aware that _Material_ contains both item and block names. + +For entity names, see [EntityType](http://jd.bukkit.org/rb/apidocs/org/bukkit/entity/EntityType.html). + +### Other changes + +* Added a setting to permit "fake players" to bypass protection. Fake players are used by some servers and some mods to allow events to be thrown for mod blocks. WorldGuard considers a player to be a fake player if the name starts with `[` and ends with `]`. +* Added a check so players can no longer disembark in a region from their tamed animal if they don't have permission to get back on. +* Added an item pickup flag to go along with the item drop flag. +* Added a new `//running` command was added to show currently running background tasks. +* Added a blacklist `on-dispense` event. +* Added more detailed error messages when parsing the YAML regions file. +* Added the `-Dworldguard.debug.listener=true` option that can be set on your server's command line to see how WorldGuard processes events, although this will result in a lot of log messages. +* Added log messages that occur on player join when a player had auto-god mode or auto-amphibious mode. +* Fixed the heal command so that it now sets a player's health to the maximum rather than 20. +* Fixed `ProtectedCuboidRegion.getPoints()` returning points in wrong order. +* Fixed the obsidian generator disable option to also apply to tripwire. +* Fixed an error with the blocked commands flag. +* Fixed a connection leak with the MySQL region code. +* Fixed `/wg reload` sometimes not applying new changes right away. +* Changed the region removal command so that child regions are no longer removed without warning when removing the parent region. +* Changed the YAML region data save code to first write the data to a temporary file before replacing the final file. +* Changed how region data is handled when region support is disabled: it is no longer loaded. +* Changed sponges to now be disabled by default. However, if you had previously installed WorldGuard, sponges will still be enabled. Sponges are being added in Minecraft and they work differently than in Minecraft Classic (which WorldGuard emulates). In addition, enabling sponge support in WorldGuard incurs extra CPU cost even if no sponges exist in the world. +* Changed how region data is loaded and saved so failures are now more graceful. If the region data fails to load for a world, then the entire world will be protected by default. In addition, periodic attempts will be made to load the region data in the background. +* Changed the `FEED` flag to maximize the player's saturation level if the hunger level is raised. +* Changed the `wg-invincible` and `wg-amphibious` groups feature so that it now must be enabled in the configuration. +* Removed the ability for WorldGuard to upgrade from a version for Minecraft Alpha. + +### API changes + +* You can use a new `WorldGuardPlugin.createProtectionQuery()` method to easily check build permissions. +* The listener classes were moved. Consider them internal -- if you want to make use of the listener somehow, please put in a ticket in our issue tracker. +* Much of the protection-related handling was previously scattered throughout the code. Now WorldGuard funnels events into an internal event system and then processes protection queries in a more consistent matter. These events are for internal use so it is strongly recommended that you do not use them (they may change without notice drastically). A lot of the protection-related flag handling was moved there. +* A significant portion of classes under the package of `com.sk89q.worldguard.protection` were changed or removed. However, the basic calls (testing the value of a flag, etc.) should still work, but they may be deprecated. Some of the utility `Domain`-related classes were removed. +* The old region database (load/save) API was removed and replaced with a new one. The new one no longer needs to pointless store a collection of regions on itself -- it has `collection = load()` and `save(collection)` methods. +* Previously, there were subclasses of `RegionManager` that implemented different spatial indices. However, now `RegionManager` is a final class and the spatial indices are separate (see the `RegionIndex` interface). +* A hash table was implemented that caches a list of regions within each loaded chunk. This increases performance of spatial queries substantially. +* A cache of recent spatial queries now exists (a cache of `ApplicableRegionSets`), but you need to make use of `RegionQuery` to make use of this cache. However, if you need to do a spatial query to modify region data, then the old method is better (get a `RegionManager`, etc.). +* `ApplicableRegionSet` was converted into an interface. In addition, there are now "virtual" `ApplicableRegionSet` instances that are returned by `RegionQuery` for certain special cases (such as, for example, region support being disabled by the user or region data being unavailable for a world due to an error). A new `isVirtual()` method is available. This is not an issue if you are getting `ApplicableRegionSet` instances from a `RegionManager`. +* `ApplicableRegionSet` now can accept a pre-sorted collection and its use of a `TreeSet` was removed for performance reasons. +* `FileNotFoundException` is now properly handled in the YAML region file class. It creates the new file and continues on. +* Player names are deprecated by Mojang and so they need to be converted into UUIDs. As this will take some time, you should do this conversion in a background task. To check whether a region contains a player, it recommended that `contains(LocalPlayer)` be used because it's possible that the user may be still using names. A new `WorldGuardPlugin.wrapOfflinePlayer()` method was added for this specific purposes. +* All flags now use `RegionGroup.ALL` as the default region group. +* `StateFlag.getDefault()` now returns a `State` rather than a `boolean`. +* The priority R-tree index now uses the R-tree for region intersection queries. Previously, it was only used for single point containment queries. +* Regions now keep a "dirty" flag and changes it whenever data within the region is changed. When WorldGuard decides to periodically save region data, it may only save changed regions. Calling `save()` on the `RegionManager` is no longer needed as periodical saves happen automatically. +* The old region data migration API was removed in favor of region store drivers. There is no longer a need to have an implementation for every possible conversion (i.e. YAML -> MySQL, MySQL -> YAML, etc.). +* `ProtectedRegion.copyFrom()` was added to copy region settings from another region. +* `@Nullable` annotations were added throughout the code. If you use IntelliJ IDEA, you may notice that it now warns you when a return value may be `null`. + +## 5.9 + +* Added a config option to allow explosion flags to only prevent block damage +* Added allow-tamed-spawns setting, on by default. This will stop WorldGuard from culling tamed animals. +* Added config option and flag to prevent soil from drying naturally. +* Added formatting codes, &k/l/m/n/o and &x (reset). +* Added snow-fall-blocks option to config to restrict snow fall to certain blocks +* Allow players to add newlines (\n) via command, not just manually in yml. +* Allow the console to load/save all region managers with one command. +* Changed entity report format slightly. +* Check bypass perms for item-drop flag. +* Check for entities and projectiles removing items from frames too. +* Fix being able to use bonemeal to turn tall grass into double plants. +* Fix number of arguments for migratedb command. +* Fixed addowner/addmember commands adding command artifacts. Specifically, "-w " will no longer be added when using it. +* Fixed milking cows in protected areas due to changes in Bukkit. +* Fixed ProtectedCuboidRegion::getPoints() returning points in wrong order. +* Fixed snowman trails being treated as snow fall. +* Make /rg list default to own regions if the player doesn't have permission otherwise. +* Protected items in item frames in protected regions. +* Resolved an issue where explosions of type OTHER_EXPLOSION were ignoring the world configuration settings. +* Send item-use blacklist event when right-clicking entities. +* Transformed the BlockFadeEvent handler into a switch. + +## 5.8 + +* BREAKING CHANGE: This version has been built for versions of Bukkit for MC 1.6.1 and newer. Do not try to use this version of WG on an older Bukkit version. +* Added support for Minecraft 1.6+. Actual support of new 1.6 additions is dependant on Bukkit. Please use a newer build of Bukkit if, for example, you seem to be unable to add "Horses" to your lists of entities. +* Added mobs.disable-snowman-trails to disable the trails left by walking snowmen. +* Added -a flag to /region removeowner and /region removemember to remove all objects. +* Teleporting is now handled by entry/exit flags. If this is causing people to get entirely stuck inside entry/exit deny regions, set the new setting 'use-player-teleports' to 'false' in your configuration. +* Updated formatting for region commands, especially /region info. +* Changed permissions checking for /region commands to be more consistent, checking for region IDs as a sub-node. This means that if some permissions for some region commands no longer work, add '.*' to the end of permission lines for WG in your permission plugin. + +## 5.7.5 + +* Refixed a few issues from 5.7.4. + +## 5.7.4 + +* BREAKING CHANGE: Removed the cyan "**" in front of greeting and farewell messages to allow more customizability. You can re-add them by adding "&c**" to the flag. +* Fixed an issue enderpearling inside exit/entry deny regions. +* Added checks to players teleporting in and out of exit/entry regions. +* Fix bad matching in blocked and allowed command flags. +* Fixed heal flag bugs when player max health was increased. +* Add fishing to xp blocker. +* Applied other-explosion flag to entity damage as well. +* Fixed an issue with interaction that should allow blacklisting item use actions with no block involved (eg fishing rods, potions) +* Fix witherskull/fireball settings not being differentiated in one case +* Allow cow milking in no-build areas +* Fix infinite durability breaking items instantly. + +## 5.7.3 + +* Fixed the error caused by Bukkit's change of the TNT minecart API. + +## 5.7.2 + +* Updated to MineCraft 1.5. +* Added TNT Minecart support. They are handled with the same flags/settings as TNT. +* Added mining and smelting xp handling to the xp drops flag and setting. +* Fixed a few edge cases for placing items on region borders. +* Legacy: Fixed issue with //stack not respecting item metadata. + +## 5.7.1 + +* Added a vine growth config option and flag. +* Added support for many commands to be used from console. Usage is with -w flag and a world name e.g. /rg addmem -w /rg flag -w +* Fixed bug that blocks placing plants in flower pots. +* Fixed permissions check for /rg tp +* Fixed enderchests not being accessible (they are now part of the USE flag) +* Fixed an issue with slabs being placable in protected regions. This was also fixed in CraftBukkit but has been left in for users running other versions. +* Fixed wither skulls being handled with other fireballs, despite having their own settings. +* Fixed item frames being placable on region borders. +* Fixed a bug that made explosions more ineffective than they should have been. +* Fixed bonemeal usage in protected regions. +* Fixed special minecarts being placable in protected regions. +* Fixed bugs being placable on region borders. +* Fixed wolves bypassing pvp flags. + +## 5.7 + +* Fixed thread sync issue with FlagStateManager. +* Fixed dragon egg region protection not working in certain cases. +* Added beacon and anvil usage protection in regions. +* Add mobs.block-enderdragon-portal-creation to stop enderdragons from making portals on death. +* Fix TNT being lethal in some case when damage was disabled. +* Fix snowballs and enderpearls being able to knock players back in pvp deny regions. +* Add subcommand support for allowed and blocked command flags. +* Add max-players-allowed and max-players-reject-message flags to limit the numbers of players in a region. +* Fix players being able to plant cocoa beans in protected areas. +* Add an enderpearl flag to prevent players from teleporting to and from regions. +* Added mobs.block-other-explosions config setting and other-explosion flag to block explosions caused by uncovered things, such as other plugins. (It doesn't actually have to be a mob) +* Fix mob damage setting and flag not blocking the DoT wither effect. +* Ghast fireball flag will not block blaze fireballs from lighting things on fire. +* Fixed players without region permissions being able to trigger tripwires, use enderchests, use wooden buttons, and put plants in flower pots. +* Added mobs.block-zombie-door-destruction setting to stop zombies from breaking down doors while difficulty is set to hard. + +Thank you to thvortex, Dilandau, tophathacker, and Dark_arc for their contributions this release. + +## 5.6.5 + +* Fixed a regression that caused paintings and item frames to no longer be properly protected. + +## 5.6.4 + +* Added mobs.block-above-ground-slimes to block slimes spawning naturally above ground. +* Added security.deop-everyone-on-join to de-op all players when they join. +* Added security.block-in-game-op-command to prevent usage of /op from in-game. +* Fixed a NullPointerException in PlayerInteract involving water potions and potion blocking. +* Fixed an issue with snowballs being blocked in regions. +* Changed dragon eggs to be checked like it is a broken block in regions. +* Moved developer's WGBukkit class to package com.sk89q.worldguard.bukkit. + +## 5.6.3 + +* Added helper WGBukkit class to get references to WorldGuard from other plugins. +* Added physics.vine-like-rope-ladders to make ladders work like vines. As long as the top ladder block is present, ladder blocks will hold even if they have no block behind them, like a rope ladder. +* Fixed NullPointerException in PlayerInteractEvent caused by potion blocking. +* Fixed mobs.block-{item-frame,painting}-destroy not working. + +Thank you to def for testing the above fixes and features. + +## 5.6.2 + +* Fixed potion blocking not working well for splash potions. + +## 5.6.1 + +* Added a mycelium-spread region flag. +* Added a dynamics.disable-mycelium-spread configuration setting. +* Added new gameplay.block-potions option. For example: gameplay: block-potions: [invisibility, speed] +* Fixed a bug in the entity explosion event handler that caused a NullPointerException. + +Thank you for gabizou for his contribution in this release. + +## 5.6 + +* Added official support for Minecraft 1.4. Support for 1.3.2 (and below) is still provided in this release. +* Added support for new and existing explosion types. +* Added support for blocking withers with the following settings: mobs: block-wither-explosions: false block-wither-block-damage: false block-wither-skull-explosions: false block-wither-skull-block-damage: true +* Added support for blocking item frames (and their use) in protected regions. +* Added item frame destruction region flags (if you wish to disable item frame destruction completely). +* (Re-)added support for priority R-tree region indexes. This means quicker region lookup, and better support for servers with thousands of regions. +* Improved /region info output for -g group flags. +* Allowed removing of -g group flags with /region flag. +* Allowed /region flag to set group and value at once. +* Deleting a region flag now also deletes group flag. +* Fixed detection of attack by projectiles to be more accurate. +* Fixed support for saving non-string flags in the MySQL region support. +* Fixed spurious comma in "Flags:" /region info output. +* Fixed players who are not in god mode not receiving damage in PVP areas from enderpearls. +* Players will now be told that they are in a no-PvP area if they are in one and attempting to attack someone, or that the player they are attacking is in a no-PvP area, if that is the case instead. +* Fixed an issue where region.getId() was lowercase'd each pass of a for loop in MySQLDatabase.updatePoly2dPoints(). +* Fixed mob damage from projectiles not being properly blocked in regions where applicable. +* Changed default region wand to leather (#334). + +Thank you to the following individuals for their contribution: +thvortex, Rutr, Glitchfinder, and DarthAndroid. + +## 5.5.4 + +* Minecraft 1.3 support. +* Fixed /stack dupe bugs. +* Added regions.use-creature-spawn-event. +* Update for Async chat issue. +* Add use flag to cakes. +* Report number of plugins loaded in report writer. +* Updated fireblal item blocking. +* Fixed permissions for /region reload. + +## 5.5.3 + +* Added game mode flag. +* Added item drop pickup flag to regions. +* Added support for fireball item blocking (385). +* Added configuration option to allow creature spawns from plugins. +* Added host key check. +* Added a new line split feature to region greetings. +* Prevented commands in the allow commands list from being blocked and vice versa. + +## 5.5.2 + +* Added potion-splash flag to block splash potions +* Add entity-painting-destroy which can be used to prevent entities from destroying paintings (skeletons firing arrows) +* Added send-chat and receive-chat flags +* Fixed having regions in multiple worlds with the same name not working with MySQL +* A cancellable event is now fired if WorldGuard disallows PvP. (for developers!) +* Made /region setparent check worldguard.region.setparent.own/member. instead of worldguard.region.setparent.own/member.. +* Allowed passing # in place of a region ID, where is the index as displayed by /region list. +* Fixed an error that occured when clearing a non-existant region's parent. +* Added a -s flag to /region teleport, which sends you to the spawn point instead of the teleport point of a region. +* Added region/role-based permissions to /region teleport. +* BREAKING CHANGE: Added a LocationFlag class and made the teleport and spawn flags use it. +* /region info -s now selects the region being queried. +* Added /region teleport . +* Vector flags can now be set to the current location with the value "here" and to a specific position with x,y,z. +* Made /region info show the region you're in if you don't specify an id explicitly. +* Prevent block ignition from lightning strikes if lightning is blocked in region. Fixes #1175 +* Correctly check both WEPIF and superperms in WorldGurdPlugin.broadcastNotification() +* Destroy fire blocks when fire-spread is disallowed +* Added a construct flag that can be used to restrict block placing/destroying in zones to certain roles. +* Added FallingSand to list of intensive entities to be removed with /halt-activiy. +* Replaced all usage of CreatureType by EntityType. +* Updated to support Bukkit 1.2-R0.3 and newer +* Made auto-god-mode disabled by default again. +* Added sign protection check disable. +* Cleaned up javadoc, deprecated duplicate methods, other cleanup +* Added a RegionGroupFlag to evey region flag Using the /region flag command with -g, the region group that the region flag applies to can be set. + +## 5.5.1 + +* Fixed attacking with arrows from non-PVP to PVP areas +* Compatibility with Bukkit 1.1-R5 +* Removed broken suppress-tick-sync-warnings config option. See bukkit.yml's setings.warn-on-overload option instead. +* Add region and 'can build' information to CommandBook's /whois + +Contributions thanks to: NolanSyKinsley + +## 5.5 + +* Removed WorldEdit.jar from the classpath to stop conflicts with Bukkit's plugin loader +* Now using Bukkit's tagged logging +* Updated to new event system +* Flush player flag state cache on world change +* Improved compatibility with versions of CommandBook without GodComponent and updated pom dep for 2.0 +* Added MySQL region storage method. +* Made explosions display their animations even when the event was blocked. +* Added fallback support for those who don't have a version of CommandBook with GodComponent +* Added LIGHTNING flag to DefaultFlag's flagsList Fixes #1026 +* Removed /god, /heal, /locate, and /stack from WorldGuard to CommandBook. CommandBook is now checked to see whether a player is godded. +* Removed remaining usages of org.bukkit.util.config.Configuration +* Removed some unused configuration options +* Added permission worldguard.region.wand to the region wand +* Fixed ProtectedRegion.compareTo. +* Fixed FlatRegionManager.getApplicableRegions to return parent regions as well. +* Added exp-drops flag to disable experience drops per-region. +* Fixed some warnings. +* Updated dependency version for WorldGuard from 5.0 to 5.1-SNAPSHOT. +* Added support for fireballs shot by players being blocked by the PVP flag. +* Now using dynamic command registration +* Split off ender dragon block damage from creeper block damage. + +Contributions thanks to: narthollis, DarkArc, Wolvereness, and skeight + +## 5.4 + +* Fixed configuration generation on Windows +* Fixed intersection calculation for regions +* Fixed timer and feeding delay for food flags +* Fixed region managers not being created properly (This caused errors related to CREATURE_SPAWN) +* Added support for Minecraft 1.0.1 blocks +* Added support for enchantments and tool damage (worldguard.stack.damaged is the node) with /stack +* Made being in the wg-invincible group respect the configuration's auto-invincible setting +* Added per-group region claim limit maximum +* Added worldguard.region.addowner.unclaimed.* permission for non-economy region 'buying' +* Checking allowed/blocked commands now occurs earlier to override more plugins +* Made PVP flag check whether either player is in a pvp deny region for blocking + +Contributions thanks to: epuidokas, wizjany, Turtle9598, and DerFlash + +## 5.3 + +* Added new config options and flags to better handle explosions of all types +* Added config options and flags for snow, ice, mushroom spread, grass spread, and leaf decay +* Added min and max health flags to clamp the values for the existing healing flags +* Added hunger flags similar to the existing healing flags +* Added an option to disable the use of the move event. This can boost performance on large servers but will disable the greeting/farewell and entry/exit flags +* Added flag to prevent pistons (including those that are outside the region, but would affect blocks inside the region) +* Added a /wg flushstates [player] command to free players stuck in a region that was exit-deny but no longer exists or the flag was removed +* Added a config option to disable xp orb dropping +* Added config option to disable redstone wire obsidian generators +* Added a vehicle-destroy flag +* Added enderman protection config and flag +* Allowed and blocked command flags will now fall back to the global region +* The worldguard.region.flag.own/member permissions are now worldguard.region.flag.regions.own/member! Please change your permissions accordingly. +* Made //stack respect max stack sizes unless the player has the worldguard.stack.illegitimate permission +* Setting invincibility flag to deny will override god mode unless the player has worldguard.god.override-regions +* The sleep flag will now prevent beds in the nether from exploding if set to deny +* Existing regions can no longer be accidentally overwritten. Use /region redefine to move an existing region. +* The info command will now sort players alphabetically +* Priming TNT by punching it with flint and steal should throw a blacklist event (consistent with old Minecraft versions) +* The /stoplag command will now remove xp orbs +* Permissions system will use per-world permissions if the provider supports it +* Players in vehicles are now subject to entry/exit and greeting/farewell flags +* Fixed interact blacklist event not logging correctly +* Fixed heal flag taking effect half a block northeast of the region +* Fixed a infinite sign dropping bug +* Fixed players being able to douse fire by punching it in regions they couldn't build it +* Fixed players being able to eat protected cake +* Fence gate usage can be blocked with the use flag +* Fixed chests being unprotected when they should be in 1.8 +* Fixed weather config settings not applying when a world was loaded +* Fixed explosion handling in certain cases +* Fixed arrows working in non-pvp zones +* Fixed buckets working on the border of protected regions in certain cases +Contributions thanks to: +wizjany, zml2008, imjake9, Droolio, epuidokas, +EduardBaer, TomyLobo, and halvors + +## 5.2.2 + +* Changed configuration saving so empty lists will be added to the configuration files. + +## 5.2.1 + +* Add region bounds to /region info +* Added the ability to add owners/members to the global region for handling guest groups. Now if there's owners or members on the global region, then those that are not on the list cannot build. +* Fixed some flag algorithm issues. +* API: Fixed DefaultDomain.size() not counting players. +* API: Undeprecated ApplicableRegionSet.allows(StateFlag). + +## 5.2 + +* Gave the ability to use color colors and macros in greeting and farewell messages. +* Added disable-ice-melting, disable-snow-formation, disable-mushroom-spread, disable-snow-melting, disable-ice-formation. +* Added paintings to blacklist support. +* Added default.disable-health-regain. +* Added auto-invincible-permission setting, which lets you use the permission 'worldguard.auto-invincible' to become invincible on join automatically. +* Flames won't appear if you are /god'ed now. +* Added blocked-cmds region flag to block commands. +* Added allowed-cmds region flag to whitelist commands. +* Added entry region flag to block entry. Use entry-group to determine who this affects. +* Added exit region flag to block exit. Use exit-group to determine who this affects. +* Made negative healing possible via the healing flag. +* Changed the configuration header messages to be more helpful to new users. +* Added a warning about modifying region files by hand. +* Improved the API for plugin developers. + +## 5.1 + +* A lot of changes. + +## 4.0-alpha1 + +* This release is only to fix WorldGuard for Bukkit. Ignore the 4.0 label. Just move along. Nothing to see. + +## 3.2.2 + +* Changed disable-water-damage to disable-drowning-damage. +* Fixed suffocation disable option. +* Fixed fire spread blocking. +* stopfire and allowfire now work in server console, just like in hMod. + +## 3.2.1 + +* Updated for recent Bukkit ItemStack change. + +## 3.2 + +* Added regions.default.pvp to control default PvP setting. +* Updated WorldGuard for the newer builds of Bukkit. +* Fixed creeper explosion blocking. +* Added redstone support to sponges, removed classic water. +* Fixed item durability disable for hoes. + + +## 3.1.2 + +* Increased the priority of WorldGuard's event handling. + +## 3.1.1 + +* Fixed creeper and chest flag conflicting. + +## 3.1 + +* Added ability to disable chest protection. New configuration setting (regions.build.chest-access) and new chest flag. +* Possibly fixed projectile blocking in no-PvP zones. +* Added /regionmembership permission that lets people only change the owners and members of regions they own. + +## 3.0b2 + +* Fixed issue where built-in permissions were not (re)loaded. +* Fixed /god not working for others. + +## 3.0b1 + +* Added parent-child relationships to regions. +* Added global build flag. +* Improved flag support. +* Fixed no-PvP zone messages being sent to the wrong person. +* Fixed commands so that they work in a recent version of Bukkit. +* Fixed NullPointerException in explode hook. + +## 2.3.1 + +* Fixed various cast exceptions with the blacklist loggers. +* Added checks for unchecked exceptions when resolving permissions. +* Fixed the item durability disable setting changing the damage value on an empty hand. + +## 2.3 + +* /god now works very well. +* Item (TNT, flint and steel, etc.) blocking should now work. +* The TNT blocking configuration variable should now work. +* Re-added /heal (not sure where that went), added /slay. +* Implemented contact damage disable toggle. +* Made all player damage disabling configuration parameters work. +* Fixed teleport-on-suffocation setting. +* Added /locate , /locate , and /locate that lets you program/change your compass bearing to point to a player's position (not yet live), a position, or back to spawn. +* Fixed CSVDatabase spitting an error if the owners list was empty. + +## 2.2 + +* Now supports GroupUsers. +* Added support for blocking creeper explosions. +* Fixed a bug where a player's groups were not being loaded correctly, causing the blacklist group ignore option to not work. + +## 2.1.1 + +* Fixed fire spread disable preventing flint and steel too. +* Fixed NullPointerException related to the player item event. +* Made setup simpler. + +## 2.1 + +* Improved Bukkit support. + +## 2.0beta3 + +* Fixed NullPointerException inside the ignite hook. + +## 2.0beta2 + +* Fixed a NullPointerException in the damage hook. + +## 2.0beta1 + +* Added area protection. Use /region or /rg to define regions. + +## 1.8.1 + +* Fixed NullPointerException. + +## 1.8 + +* Updated for Minecraft beta. +* Blacklist hooks have CHANGED. The new hooks are work better. on-create has been replaced with on-place (for block placement) and on-use (for item usage, light buckets and lighters). The old on-use is now on-right-click although it currently does nothing. +* Added coordinates to blacklist file logger. +* Added suffocation damage prevention. + +## 1.7.1 + +* Updated for v0.2.8. + +## 1.7 + +* Updated for v0.2.6_02 and hMod b129. +* Added option to prevent water from damaging particular blocks (Redstone wire, Minecart tracks, etc.). See disable-water-damage-blocks. +* /reload WorldGuard should now reload WorldGuard's configuration. +* Added wg-invincible and wg-amphibious groups that let you give certain players invincibility or underwater breathing. +* Added the ability to disable fall, water, lava, and/or fire damage. +* Water/lava bucket blocking should now be more reliable. +* Added /stack (alias /;) commands to stack items in your inventory into piles up to 64 items in size. Even unstackable items like signs can be stacked. +* /god mode should now let you gain health. +* /god mode should now work more reliably. +* Caught an error message for when the configuration file does not exist. + +## 1.6 + +* Added spawn protection that prevents damage when a player spawns. +* Added login protection that prevents damage when a player joins. +* Added /god. /god can be used on others, provided that the /godother permission is set for the command's user. + +## 1.5.1 + +* Removed the 'java.lang.NoSuchMethodError: Item.setDamage(I)V' error generated. + +## 1.5 + +* Removed the block lag fix. +* WorldGuard now conditionally loads features so you can use any up-to-date build of hMod and the unsupported features won't break the entire plugin. When WG loads, check the server console for a report of features that are not supported by your version of hMod. +* Changed some configuration defaults: disable-lava-fire=true simulate-sponge=true item-durability=false + +## 1.4.1 + +* Fixed issue with the block lag fix dropping item stacks of 0 quantity. + +## 1.4 + +* New on-acquire hook. This does both a total inventory check on inventory change and item pick up denial. The item pick up denial is largely thanks to Dinnerbone for fulfilling my request to add a particular hook. +* Item durability can now be toggled with item-durability. Thanks to Dinnerbone for figuring this out while filfilling my request. The default setting is to fix item durability. +* New no-physics-gravel and no-physics-sand options allow you to prevent gravel and sand from falling. +* New allow-portal-anywhere option allows you to create portal blocks anywhere. + +## 1.3 + +* UPGRADE WARNING: If you are using database logging, you must add a NULL 'comment' VARCHAR field to the table. The line of SQL that you can run for MySQL can be found in blacklist_table.sql. +* Improved plugin compatibility with the block lag fix. For maximum compatibility, you must now list WorldGuard as the first plugin in server.properties and you cannot reload or enable the plugin while the server is up. This is for bad plugins that unnecessarily use the "critical" priority for its hooks like the LWC. Most plugins don't do that though, so this won't apply for most people. +* Fixed signs being blank if they were denied via the blacklist. +* Added on-break event to the blacklist. +* Added comments and messages. Comments are only for your own use and they are printed with the log and notify actions of the blacklist. The message replaces the message that is shown the user and it is optional. +* The blacklist notification messages are now more compact and are light grey instead of light purple. +* The notify, log, and tell actions will now trigger every time for the on-break, on-create, on-drop events rather than wait 3 seconds between events for the same item or block for a player. + +## 1.2 + +* /stopfire and /allowfire disable and enable fire spreading globally. +* Block lag fix slightly improved in accuracy for item drops. +* Sponge updated to remove water when the sponge block is set. Sponge radius can now be controlled using the 'sponge-radius' parameter and the default is now set to simulate Classic. +* Updated for a newer build of "b126," meaning that lava spread control now works well! +* A new summary of the status of some core protections is now printed on start. Disable this with 'summary-on-start'. +* Blacklist system has been overhauled. Check README.txt for changed configuration settings! +* The blacklist's method of preventing notification repeats is now better, instead waiting 3 seconds before notifying again (before it didn't notify again at all unless the user started using another blocked action). +* To give users the ability to receive notifications, the command to give permission to has been changed to /worldguardnotify, although the old one (that was never mentioned anywhere) still works. +* Water and lava buckets are now psuedo-blocked using an unreliable method that risks the stability of your server (no other plugin does it better though). Use it as your own risk. +* Added on item drop and on item use (i.e. chest) events. +* Chests, signs, and furnaces can now be blocked better with the blacklist system. +* The event names in the blacklist configuration have changed but the old event names should still work. The new names should make "more sense." +* A new "ban" action has been added to the blacklist. +* Action messages have been improved, now longer saying "destroyed" for everything. +* Logging to file has been completely changed, allowing you to use the date and time and the player's username in the log filename. It no longer rotates log files based on size, however. +* Logging to database is now supported. +* Tools can now be destroyed on drop to alleviate the durability cheat. You can do this with either with the blacklist or with the 'item-drop-blacklist' configuration option. The configuration option prints more friendly messages than the 'tell' action of blacklists. + +## 1.1.2 + +* Block lag fix: Snow and glass should no longer drop items. +* Block lag fix: Block object now reset after hook call. + +## 1.1.1 + +* Fixed redstone wire dropping wood blocks with the block lag fix. +* The block lag fix now consults other plugins so that ALL protection mechanisms and plugins should work. + +## 1.1 + +* Added block lag fix. + +## 1.0 + +* Initial release. diff --git a/sk89q-worldguard/CONTRIBUTING.md b/sk89q-worldguard/CONTRIBUTING.md new file mode 100644 index 000000000..61233c122 --- /dev/null +++ b/sk89q-worldguard/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# Contributing + +Thank you for your interest in contributing to WorldGuard! We appreciate your +effort, but to make sure that the inclusion of your patch is a smooth process, we +ask that you make note of the following guidelines. + +* **Follow the [Oracle coding conventions](https://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html).** + We can't stress this enough; if your code has notable issues, it may delay + the process significantly. +* **Target Java 8 for source and compilation.** Make sure to mark methods with + ` @Override` that override methods of parent classes, or that implement + methods of interfaces. +* **Use only spaces for indentation.** Our indents are 4-spaces long, and tabs + are unacceptable. +* **Wrap code to a 89 column limit.** We do this to make side by side diffs + and other such tasks easier. Ignore this guideline if it makes the code + too unreadable. +* **Write complete Javadocs.** Do so only for public methods, and make sure + that your `@param` and `@return` fields are not just blank. +* **Don't tag classes with @author.** Some legacy classes may have this tag, + but we are phasing it out. +* **Make sure the code is efficient.** One way you can achieve this is to spend + around ten minutes to think about what the code is doing and whether it + seems awfully roundabout. If you had to copy the same large piece of + code in several places, that's bad. +* **Keep commit summaries under 70 characters.** For more details, place two + new lines after the summary line and write away! +* **Test your code.** We're not interested in broken code, for the obvious reasons. +* **Write unit tests.** While this is strictly optional, we recommend it for + complicated algorithms. + + +Checklist +--------- + +Ready to submit? Perform the checklist below: + +1. Have all tabs been replaced into four spaces? Are indentations 4-space wide? +2. Have I written proper Javadocs for my public methods? Are the @param and + @return fields actually filled out? +3. Have I `git rebase`d my pull request to the latest commit of the target + branch? +4. Have I combined my commits into a reasonably small number (if not one) + commit using `git rebase`? +5. Have I made my pull request too large? Pull requests should introduce + small sets of changes at a time. Major changes should be discussed with + the team prior to starting work. +6. Are my commit messages descriptive? + +You should be aware of [`git rebase`](http://learn.github.com/p/rebasing.html). +It allows you to modify existing commit messages, and combine, break apart, or +adjust past changes. + +Example +------- + +This is **GOOD:** + + if (var.func(param1, param2)) { + // do things + } + +This is **EXTREMELY BAD:** + + if(var.func( param1, param2 )) + { + // do things + } diff --git a/sk89q-worldguard/HEADER.txt b/sk89q-worldguard/HEADER.txt new file mode 100644 index 000000000..b80194ec9 --- /dev/null +++ b/sk89q-worldguard/HEADER.txt @@ -0,0 +1,16 @@ +WorldGuard, a suite of tools for Minecraft +Copyright (C) sk89q +Copyright (C) WorldGuard team and contributors + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program. If not, see . diff --git a/sk89q-worldguard/LICENSE.txt b/sk89q-worldguard/LICENSE.txt new file mode 100644 index 000000000..935299678 --- /dev/null +++ b/sk89q-worldguard/LICENSE.txt @@ -0,0 +1,201 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +---------------------------------------------------------------------------- + +This work includes Robert Olofsson's Priority R-Tree implementation, for which +its license is reproduced below. + +Copyright (c) 2008-2010 Robert Olofsson. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the authors nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. \ No newline at end of file diff --git a/sk89q-worldguard/README.md b/sk89q-worldguard/README.md new file mode 100644 index 000000000..c499869b0 --- /dev/null +++ b/sk89q-worldguard/README.md @@ -0,0 +1,47 @@ +

+ WorldGuard +

+ +WorldGuard lets you and players guard areas of land against griefers and undesirables, as well as tweak and disable various gameplay features of Minecraft. + +* Block creeper and wither block damage, falling damage, etc. +* Disable fire spread, lava fire spread, ice formation, Endermen picking up blocks, etc. +* Blacklist certain items and blocks so they can't be used +* Warn moderators when certain items and blocks are used +* Protect areas of your world so only certain people can build in them +* Set areas where PVP, TNT, mob damage, and other features are disabled +* Protect your server from various 'exploits' like magical obsidian creation machines +* Disable, or enable, various Minecraft features, like sponges from classic +* Add useful commands like an immediate "STOP ALL FIRE SPREAD" command +* Enable only features you want! Everything is off by default + +WorldGuard is open source and is available under the GNU Lesser +General Public License v3. + +A Bukkit server implementation (such as [Paper](https://papermc.io)) and the [WorldEdit plugin](https://dev.bukkit.org/projects/worldedit) are required to use WorldGuard. You can get a release copy of WorldGuard from the [BukkitDev site](https://dev.bukkit.org/projects/worldguard). + +Compiling +--------- + +The project is written for Java 8 and our build process makes use of +[Gradle](http://gradle.org). + +Dependencies are automatically handled by Gradle. + +Contributing +------------ + +We happily accept contributions, especially through pull requests on GitHub. + +Please read CONTRIBUTING.md for important guidelines to follow. + +Submissions must be licensed under the GNU Lesser General Public License v3. + +Links +----- + +* [Homepage](http://enginehub.org/worldguard) +* [Discord](https://discord.gg/enginehub) +* [Issue tracker](https://github.com/EngineHub/WorldGuard/issues) +* [Continuous integration](http://builds.enginehub.org) [![Build Status](https://ci.enginehub.org/app/rest/builds/buildType:bt11,branch:master/statusIcon.svg)](http://ci.enginehub.org/viewType.html?buildTypeId=bt11&guest=1) +* [End-user documentation](https://worldguard.enginehub.org/en/latest/) diff --git a/sk89q-worldguard/build.gradle.kts b/sk89q-worldguard/build.gradle.kts new file mode 100644 index 000000000..caa15ac54 --- /dev/null +++ b/sk89q-worldguard/build.gradle.kts @@ -0,0 +1,26 @@ +import org.ajoberstar.grgit.Grgit + +logger.lifecycle(""" +******************************************* + You are building WorldGuard! + If you encounter trouble: + 1) Try running 'build' in a separate Gradle run + 2) Use gradlew and not gradle + 3) If you still need help, ask on Discord! https://discord.gg/enginehub + + Output files will be in [subproject]/build/libs +******************************************* +""") + +applyRootArtifactoryConfig() + +if (!project.hasProperty("gitCommitHash")) { + apply(plugin = "org.ajoberstar.grgit") + ext["gitCommitHash"] = try { + Grgit.open(mapOf("currentDir" to project.rootDir))?.head()?.abbreviatedId + } catch (e: Exception) { + logger.warn("Error getting commit hash", e) + + "no.git.id" + } +} \ No newline at end of file diff --git a/sk89q-worldguard/buildSrc/build.gradle.kts b/sk89q-worldguard/buildSrc/build.gradle.kts new file mode 100644 index 000000000..c681a310b --- /dev/null +++ b/sk89q-worldguard/buildSrc/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + `kotlin-dsl` + kotlin("jvm") version embeddedKotlinVersion +} + +repositories { + mavenCentral() + gradlePluginPortal() +} + +dependencies { + implementation(gradleApi()) + implementation("gradle.plugin.org.cadixdev.gradle:licenser:0.6.0") + implementation("org.ajoberstar.grgit:grgit-gradle:4.1.0") + implementation("com.github.jengelman.gradle.plugins:shadow:6.1.0") + implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.21.0") +} \ No newline at end of file diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/ArtifactoryConfig.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/ArtifactoryConfig.kt new file mode 100644 index 000000000..929bab7af --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/ArtifactoryConfig.kt @@ -0,0 +1,40 @@ +import org.gradle.api.Project +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.named +import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention +import org.jfrog.gradle.plugin.artifactory.task.ArtifactoryTask + +private const val ARTIFACTORY_CONTEXT_URL = "artifactory_contextUrl" +private const val ARTIFACTORY_USER = "artifactory_user" +private const val ARTIFACTORY_PASSWORD = "artifactory_password" + +fun Project.applyRootArtifactoryConfig() { + if (!project.hasProperty(ARTIFACTORY_CONTEXT_URL)) ext[ARTIFACTORY_CONTEXT_URL] = "http://localhost" + if (!project.hasProperty(ARTIFACTORY_USER)) ext[ARTIFACTORY_USER] = "guest" + if (!project.hasProperty(ARTIFACTORY_PASSWORD)) ext[ARTIFACTORY_PASSWORD] = "" + + apply(plugin = "com.jfrog.artifactory") + configure { + setContextUrl("${project.property(ARTIFACTORY_CONTEXT_URL)}") + clientConfig.publisher.run { + repoKey = when { + "${project.version}".contains("SNAPSHOT") -> "libs-snapshot-local" + else -> "libs-release-local" + } + username = "${project.property(ARTIFACTORY_USER)}" + password = "${project.property(ARTIFACTORY_PASSWORD)}" + isMaven = true + isIvy = false + } + } + tasks.named("artifactoryPublish") { + isSkip = true + } +} + +fun Project.applyCommonArtifactoryConfig() { + tasks.named("artifactoryPublish") { + publications("maven") + } +} diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/CommonConfig.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/CommonConfig.kt new file mode 100644 index 000000000..2a01ea900 --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/CommonConfig.kt @@ -0,0 +1,38 @@ +import org.cadixdev.gradle.licenser.LicenseExtension +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.repositories +import org.gradle.kotlin.dsl.the + +fun Project.applyCommonConfiguration() { + group = rootProject.group + version = rootProject.version + + repositories { + mavenCentral() + maven { url = uri("https://maven.enginehub.org/repo/") } + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") } + } + + configurations.all { + resolutionStrategy { + cacheChangingModulesFor(5, "MINUTES") + } + } + + plugins.withId("java") { + the().toolchain { + languageVersion.set(JavaLanguageVersion.of(16)) + } + } + + apply(plugin = "org.cadixdev.licenser") + configure { + header(rootProject.file("HEADER.txt")) + include("**/*.java") + include("**/*.kt") + } +} diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/GradleExtras.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/GradleExtras.kt new file mode 100644 index 000000000..e7d1e0ede --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/GradleExtras.kt @@ -0,0 +1,12 @@ +import org.gradle.api.Project +import org.gradle.api.plugins.ExtraPropertiesExtension +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.the + +val Project.ext: ExtraPropertiesExtension + get() = extensions.getByType() + +val Project.sourceSets: SourceSetContainer + get() = the().sourceSets diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/LibsConfig.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/LibsConfig.kt new file mode 100644 index 000000000..3a78ce604 --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/LibsConfig.kt @@ -0,0 +1,171 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.attributes.Bundling +import org.gradle.api.attributes.Category +import org.gradle.api.attributes.DocsType +import org.gradle.api.attributes.LibraryElements +import org.gradle.api.attributes.Usage +import org.gradle.api.attributes.java.TargetJvmVersion +import org.gradle.api.component.AdhocComponentWithVariants +import org.gradle.api.component.SoftwareComponentFactory +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.Jar +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.invoke +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import javax.inject.Inject + +fun Project.applyLibrariesConfiguration() { + applyCommonConfiguration() + apply(plugin = "java-base") + apply(plugin = "maven-publish") + apply(plugin = "com.github.johnrengelman.shadow") + apply(plugin = "com.jfrog.artifactory") + + configurations { + create("shade") + } + + group = "${rootProject.group}.worldguard-libs" + + val relocations = mapOf( + "org.enginehub.squirrelid" to "com.sk89q.worldguard.util.profile" + ) + + tasks.register("jar") { + configurations = listOf(project.configurations["shade"]) + archiveClassifier.set("") + + dependencies { + exclude(dependency("com.google.code.findbugs:jsr305")) + } + + relocations.forEach { (from, to) -> + relocate(from, to) + } + } + val altConfigFiles = { artifactType: String -> + val deps = configurations["shade"].incoming.dependencies + .filterIsInstance() + .map { it.copy() } + .map { dependency -> + dependency.artifact { + name = dependency.name + type = artifactType + extension = "jar" + classifier = artifactType + } + dependency + } + + files(configurations.detachedConfiguration(*deps.toTypedArray()) + .resolvedConfiguration.lenientConfiguration.artifacts + .filter { it.classifier == artifactType } + .map { zipTree(it.file) }) + } + tasks.register("sourcesJar") { + from({ + altConfigFiles("sources") + }) + relocations.forEach { (from, to) -> + val filePattern = Regex("(.*)${from.replace('.', '/')}((?:/|$).*)") + val textPattern = Regex.fromLiteral(from) + eachFile { + filter { + it.replaceFirst(textPattern, to) + } + path = path.replaceFirst(filePattern, "$1${to.replace('.', '/')}$2") + } + } + archiveClassifier.set("sources") + } + + tasks.named("assemble").configure { + dependsOn("jar", "sourcesJar") + } + + project.apply() + + val libsComponent = project.components["libs"] as AdhocComponentWithVariants + + val apiElements = project.configurations.register("apiElements") { + isVisible = false + description = "API elements for libs" + isCanBeResolved = false + isCanBeConsumed = true + attributes { + attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_API)) + attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) + attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling.SHADOWED)) + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.JAR)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) + } + outgoing.artifact(tasks.named("jar")) + } + + val runtimeElements = project.configurations.register("runtimeElements") { + isVisible = false + description = "Runtime elements for libs" + isCanBeResolved = false + isCanBeConsumed = true + attributes { + attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY)) + attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling.SHADOWED)) + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.JAR)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 8) + } + outgoing.artifact(tasks.named("jar")) + } + + val sourcesElements = project.configurations.register("sourcesElements") { + isVisible = false + description = "Source elements for libs" + isCanBeResolved = false + isCanBeConsumed = true + attributes { + attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.DOCUMENTATION)) + attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling.SHADOWED)) + attribute(DocsType.DOCS_TYPE_ATTRIBUTE, project.objects.named(DocsType.SOURCES)) + } + outgoing.artifact(tasks.named("sourcesJar")) + } + + libsComponent.addVariantsFromConfiguration(apiElements.get()) { + mapToMavenScope("compile") + } + + libsComponent.addVariantsFromConfiguration(runtimeElements.get()) { + mapToMavenScope("runtime") + } + + libsComponent.addVariantsFromConfiguration(sourcesElements.get()) { + mapToMavenScope("runtime") + } + + configure { + publications { + register("maven") { + from(libsComponent) + } + } + } + + applyCommonArtifactoryConfig() +} + +internal open class LibsConfigPluginHack @Inject constructor( + private val softwareComponentFactory: SoftwareComponentFactory +) : Plugin { + override fun apply(project: Project) { + val libsComponents = softwareComponentFactory.adhoc("libs") + project.components.add(libsComponents) + } +} \ No newline at end of file diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/PlatformConfig.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/PlatformConfig.kt new file mode 100644 index 000000000..659af3990 --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/PlatformConfig.kt @@ -0,0 +1,113 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.Project +import org.gradle.api.component.AdhocComponentWithVariants +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.plugins.quality.CheckstyleExtension +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.api.tasks.testing.Test +import org.gradle.external.javadoc.StandardJavadocDocletOptions +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.get +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType +import org.gradle.kotlin.dsl.the + +fun Project.applyPlatformAndCoreConfiguration() { + applyCommonConfiguration() + apply(plugin = "java") + apply(plugin = "eclipse") + apply(plugin = "idea") + apply(plugin = "maven-publish") + apply(plugin = "checkstyle") + apply(plugin = "com.jfrog.artifactory") + + ext["internalVersion"] = "$version+${rootProject.ext["gitCommitHash"]}" + + configure { + configFile = rootProject.file("config/checkstyle/checkstyle.xml") + toolVersion = "8.34" + } + + tasks.withType().configureEach { + useJUnitPlatform() + } + + dependencies { + "compileOnly"("com.google.code.findbugs:jsr305:3.0.2") + "testCompileOnly"("com.google.code.findbugs:jsr305:3.0.2") + "testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT}") + "testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT}") + "testImplementation"("org.mockito:mockito-core:${Versions.MOCKITO}") + "testImplementation"("org.mockito:mockito-junit-jupiter:${Versions.MOCKITO}") + "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT}") + } + + // Java 8 turns on doclint which we fail + tasks.withType().configureEach { + (options as StandardJavadocDocletOptions).apply { + addStringOption("Xdoclint:none", "-quiet") + tags( + "apiNote:a:API Note:", + "implSpec:a:Implementation Requirements:", + "implNote:a:Implementation Note:" + ) + } + } + + the().withJavadocJar() + + if (name == "worldguard-core" || name == "worldguard-bukkit") { + the().withSourcesJar() + } + + tasks.named("check").configure { + dependsOn("checkstyleMain", "checkstyleTest") + } + + configure { + publications { + register("maven") { + from(components["java"]) + versionMapping { + usage("java-api") { + fromResolutionOf("runtimeClasspath") + } + usage("java-runtime") { + fromResolutionResult() + } + } + } + } + } + + applyCommonArtifactoryConfig() +} + +fun Project.applyShadowConfiguration() { + apply(plugin = "com.github.johnrengelman.shadow") + tasks.named("shadowJar") { + archiveClassifier.set("dist") + dependencies { + include(project(":worldguard-libs:core")) + //include(project(":worldguard-libs:${project.name.replace("worldguard-", "")}")) + include(project(":worldguard-core")) + + relocate("org.flywaydb", "com.sk89q.worldguard.internal.flywaydb") { + include(dependency("org.flywaydb:flyway-core:3.0")) + } + exclude("com.google.code.findbugs:jsr305") + } + exclude("GradleStart**") + exclude(".cache") + exclude("LICENSE*") + } + val javaComponent = components["java"] as AdhocComponentWithVariants + javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) { + skip() + } +} diff --git a/sk89q-worldguard/buildSrc/src/main/kotlin/Versions.kt b/sk89q-worldguard/buildSrc/src/main/kotlin/Versions.kt new file mode 100644 index 000000000..067406eb9 --- /dev/null +++ b/sk89q-worldguard/buildSrc/src/main/kotlin/Versions.kt @@ -0,0 +1,9 @@ +object Versions { +// const val PISTON = "0.4.3" +// const val AUTO_VALUE = "1.6.5" + const val WORLDEDIT = "7.2.6-SNAPSHOT" + const val JUNIT = "5.7.0" + const val SQUIRRELID = "0.3.0" + const val GUAVA = "21.0" + const val MOCKITO = "3.7.7" +} diff --git a/sk89q-worldguard/config/checkstyle/checkstyle.xml b/sk89q-worldguard/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000..b91ea60ef --- /dev/null +++ b/sk89q-worldguard/config/checkstyle/checkstyle.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sk89q-worldguard/config/checkstyle/import-control.xml b/sk89q-worldguard/config/checkstyle/import-control.xml new file mode 100644 index 000000000..7da27610d --- /dev/null +++ b/sk89q-worldguard/config/checkstyle/import-control.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sk89q-worldguard/config/checkstyle/suppressions.xml b/sk89q-worldguard/config/checkstyle/suppressions.xml new file mode 100644 index 000000000..f01791968 --- /dev/null +++ b/sk89q-worldguard/config/checkstyle/suppressions.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/sk89q-worldguard/gradle.properties b/sk89q-worldguard/gradle.properties new file mode 100644 index 000000000..8a7a4c1d8 --- /dev/null +++ b/sk89q-worldguard/gradle.properties @@ -0,0 +1,2 @@ +group=com.sk89q.worldguard +version=7.0.6-SNAPSHOT diff --git a/sk89q-worldguard/gradle/wrapper/gradle-wrapper.jar b/sk89q-worldguard/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a GIT binary patch literal 55616 zcmafaW0WS*vSoFbZJS-TZP!<}ZQEV8ZQHihW!tvx>6!c9%-lQoy;&DmfdT@8fB*sl68LLCKtKQ283+jS?^Q-bNq|NIAW8=eB==8_)^)r*{C^$z z{u;{v?IMYnO`JhmPq7|LA_@Iz75S9h~8`iX>QrjrmMeu{>hn4U;+$dor zz+`T8Q0f}p^Ao)LsYq74!W*)&dTnv}E8;7H*Zetclpo2zf_f>9>HT8;`O^F8;M%l@ z57Z8dk34kG-~Wg7n48qF2xwPp;SOUpd1}9Moir5$VSyf4gF)Mp-?`wO3;2x9gYj59oFwG>?Leva43@e(z{mjm0b*@OAYLC`O9q|s+FQLOE z!+*Y;%_0(6Sr<(cxE0c=lS&-FGBFGWd_R<5$vwHRJG=tB&Mi8@hq_U7@IMyVyKkOo6wgR(<% zQw1O!nnQl3T9QJ)Vh=(`cZM{nsEKChjbJhx@UQH+G>6p z;beBQ1L!3Zl>^&*?cSZjy$B3(1=Zyn~>@`!j%5v7IBRt6X`O)yDpVLS^9EqmHxBcisVG$TRwiip#ViN|4( zYn!Av841_Z@Ys=T7w#>RT&iXvNgDq3*d?$N(SznG^wR`x{%w<6^qj&|g})La;iD?`M=p>99p><39r9+e z`dNhQ&tol5)P#;x8{tT47i*blMHaDKqJs8!Pi*F{#)9%USFxTVMfMOy{mp2ZrLR40 z2a9?TJgFyqgx~|j0eA6SegKVk@|Pd|_6P$HvwTrLTK)Re`~%kg8o9`EAE1oAiY5Jgo=H}0*D?tSCn^=SIN~fvv453Ia(<1|s07aTVVtsRxY6+tT3589iQdi^ zC92D$ewm9O6FA*u*{Fe_=b`%q`pmFvAz@hfF@OC_${IPmD#QMpPNo0mE9U=Ch;k0L zZteokPG-h7PUeRCPPYG%H!WswC?cp7M|w42pbtwj!m_&4%hB6MdLQe&}@5-h~! zkOt;w0BbDc0H!RBw;1UeVckHpJ@^|j%FBZlC} zsm?nFOT$`F_i#1_gh4|n$rDe>0md6HvA=B%hlX*3Z%y@a&W>Rq`Fe(8smIgxTGb#8 zZ`->%h!?QCk>v*~{!qp=w?a*};Y**1uH`)OX`Gi+L%-d6{rV?@}MU#qfCU(!hLz;kWH=0A%W7E^pA zD;A%Jg5SsRe!O*0TyYkAHe&O9z*Ij-YA$%-rR?sc`xz_v{>x%xY39!8g#!Z0#03H( z{O=drKfb0cbx1F*5%q81xvTDy#rfUGw(fesh1!xiS2XT;7_wBi(Rh4i(!rR^9=C+- z+**b9;icxfq@<7}Y!PW-0rTW+A^$o*#ZKenSkxLB$Qi$%gJSL>x!jc86`GmGGhai9 zOHq~hxh}KqQHJeN$2U{M>qd*t8_e&lyCs69{bm1?KGTYoj=c0`rTg>pS6G&J4&)xp zLEGIHSTEjC0-s-@+e6o&w=h1sEWWvJUvezID1&exb$)ahF9`(6`?3KLyVL$|c)CjS zx(bsy87~n8TQNOKle(BM^>1I!2-CZ^{x6zdA}qeDBIdrfd-(n@Vjl^9zO1(%2pP9@ zKBc~ozr$+4ZfjmzEIzoth(k?pbI87=d5OfjVZ`Bn)J|urr8yJq`ol^>_VAl^P)>2r)s+*3z5d<3rP+-fniCkjmk=2hTYRa@t zCQcSxF&w%mHmA?!vaXnj7ZA$)te}ds+n8$2lH{NeD4mwk$>xZCBFhRy$8PE>q$wS`}8pI%45Y;Mg;HH+}Dp=PL)m77nKF68FggQ-l3iXlVZuM2BDrR8AQbK;bn1%jzahl0; zqz0(mNe;f~h8(fPzPKKf2qRsG8`+Ca)>|<&lw>KEqM&Lpnvig>69%YQpK6fx=8YFj zHKrfzy>(7h2OhUVasdwKY`praH?>qU0326-kiSyOU_Qh>ytIs^htlBA62xU6xg?*l z)&REdn*f9U3?u4$j-@ndD#D3l!viAUtw}i5*Vgd0Y6`^hHF5R=No7j8G-*$NWl%?t z`7Nilf_Yre@Oe}QT3z+jOUVgYtT_Ym3PS5(D>kDLLas8~F+5kW%~ZYppSrf1C$gL* zCVy}fWpZ3s%2rPL-E63^tA|8OdqKsZ4TH5fny47ENs1#^C`_NLg~H^uf3&bAj#fGV zDe&#Ot%_Vhj$}yBrC3J1Xqj>Y%&k{B?lhxKrtYy;^E9DkyNHk5#6`4cuP&V7S8ce9 zTUF5PQIRO7TT4P2a*4;M&hk;Q7&{(83hJe5BSm=9qt~;U)NTf=4uKUcnxC`;iPJeI zW#~w?HIOM+0j3ptB0{UU{^6_#B*Q2gs;1x^YFey(%DJHNWz@e_NEL?$fv?CDxG`jk zH|52WFdVsZR;n!Up;K;4E$|w4h>ZIN+@Z}EwFXI{w_`?5x+SJFY_e4J@|f8U08%dd z#Qsa9JLdO$jv)?4F@&z_^{Q($tG`?|9bzt8ZfH9P`epY`soPYqi1`oC3x&|@m{hc6 zs0R!t$g>sR@#SPfNV6Pf`a^E?q3QIaY30IO%yKjx#Njj@gro1YH2Q(0+7D7mM~c>C zk&_?9Ye>B%*MA+77$Pa!?G~5tm`=p{NaZsUsOgm6Yzclr_P^2)r(7r%n(0?4B#$e7 z!fP;+l)$)0kPbMk#WOjm07+e?{E)(v)2|Ijo{o1+Z8#8ET#=kcT*OwM#K68fSNo%< zvZFdHrOrr;>`zq!_welWh!X}=oN5+V01WJn7=;z5uo6l_$7wSNkXuh=8Y>`TjDbO< z!yF}c42&QWYXl}XaRr0uL?BNPXlGw=QpDUMo`v8pXzzG(=!G;t+mfCsg8 zJb9v&a)E!zg8|%9#U?SJqW!|oBHMsOu}U2Uwq8}RnWeUBJ>FtHKAhP~;&T4mn(9pB zu9jPnnnH0`8ywm-4OWV91y1GY$!qiQCOB04DzfDDFlNy}S{$Vg9o^AY!XHMueN<{y zYPo$cJZ6f7``tmlR5h8WUGm;G*i}ff!h`}L#ypFyV7iuca!J+C-4m@7*Pmj9>m+jh zlpWbud)8j9zvQ`8-oQF#u=4!uK4kMFh>qS_pZciyq3NC(dQ{577lr-!+HD*QO_zB9 z_Rv<#qB{AAEF8Gbr7xQly%nMA%oR`a-i7nJw95F3iH&IX5hhy3CCV5y>mK4)&5aC*12 zI`{(g%MHq<(ocY5+@OK-Qn-$%!Nl%AGCgHl>e8ogTgepIKOf3)WoaOkuRJQt%MN8W z=N-kW+FLw=1^}yN@*-_c>;0N{-B!aXy#O}`%_~Nk?{e|O=JmU8@+92Q-Y6h)>@omP=9i~ zi`krLQK^!=@2BH?-R83DyFkejZkhHJqV%^} zUa&K22zwz7b*@CQV6BQ9X*RB177VCVa{Z!Lf?*c~PwS~V3K{id1TB^WZh=aMqiws5)qWylK#^SG9!tqg3-)p_o(ABJsC!0;0v36;0tC= z!zMQ_@se(*`KkTxJ~$nIx$7ez&_2EI+{4=uI~dwKD$deb5?mwLJ~ema_0Z z6A8Q$1~=tY&l5_EBZ?nAvn$3hIExWo_ZH2R)tYPjxTH5mAw#3n-*sOMVjpUrdnj1DBm4G!J+Ke}a|oQN9f?!p-TcYej+(6FNh_A? zJ3C%AOjc<8%9SPJ)U(md`W5_pzYpLEMwK<_jgeg-VXSX1Nk1oX-{yHz z-;CW!^2ds%PH{L{#12WonyeK5A=`O@s0Uc%s!@22etgSZW!K<%0(FHC+5(BxsXW@e zAvMWiO~XSkmcz%-@s{|F76uFaBJ8L5H>nq6QM-8FsX08ug_=E)r#DC>d_!6Nr+rXe zzUt30Du_d0oSfX~u>qOVR*BmrPBwL@WhF^5+dHjWRB;kB$`m8|46efLBXLkiF|*W= zg|Hd(W}ZnlJLotYZCYKoL7YsQdLXZ!F`rLqLf8n$OZOyAzK`uKcbC-n0qoH!5-rh&k-`VADETKHxrhK<5C zhF0BB4azs%j~_q_HA#fYPO0r;YTlaa-eb)Le+!IeP>4S{b8&STp|Y0if*`-A&DQ$^ z-%=i73HvEMf_V6zSEF?G>G-Eqn+|k`0=q?(^|ZcqWsuLlMF2!E*8dDAx%)}y=lyMa z$Nn0_f8YN8g<4D>8IL3)GPf#dJYU@|NZqIX$;Lco?Qj=?W6J;D@pa`T=Yh z-ybpFyFr*3^gRt!9NnbSJWs2R-S?Y4+s~J8vfrPd_&_*)HBQ{&rW(2X>P-_CZU8Y9 z-32><7|wL*K+3{ZXE5}nn~t@NNT#Bc0F6kKI4pVwLrpU@C#T-&f{Vm}0h1N3#89@d zgcx3QyS;Pb?V*XAq;3(W&rjLBazm69XX;%^n6r}0!CR2zTU1!x#TypCr`yrII%wk8 z+g)fyQ!&xIX(*>?T}HYL^>wGC2E}euj{DD_RYKK@w=yF+44367X17)GP8DCmBK!xS zE{WRfQ(WB-v>DAr!{F2-cQKHIjIUnLk^D}7XcTI#HyjSiEX)BO^GBI9NjxojYfQza zWsX@GkLc7EqtP8(UM^cq5zP~{?j~*2T^Bb={@PV)DTkrP<9&hxDwN2@hEq~8(ZiF! z3FuQH_iHyQ_s-#EmAC5~K$j_$cw{+!T>dm#8`t%CYA+->rWp09jvXY`AJQ-l%C{SJ z1c~@<5*7$`1%b}n7ivSo(1(j8k+*Gek(m^rQ!+LPvb=xA@co<|(XDK+(tb46xJ4) zcw7w<0p3=Idb_FjQ@ttoyDmF?cT4JRGrX5xl&|ViA@Lg!vRR}p#$A?0=Qe+1)Mizl zn;!zhm`B&9t0GA67GF09t_ceE(bGdJ0mbXYrUoV2iuc3c69e;!%)xNOGG*?x*@5k( zh)snvm0s&gRq^{yyeE)>hk~w8)nTN`8HJRtY0~1f`f9ue%RV4~V(K*B;jFfJY4dBb z*BGFK`9M-tpWzayiD>p_`U(29f$R|V-qEB;+_4T939BPb=XRw~8n2cGiRi`o$2qm~ zN&5N7JU{L*QGM@lO8VI)fUA0D7bPrhV(GjJ$+@=dcE5vAVyCy6r&R#4D=GyoEVOnu z8``8q`PN-pEy>xiA_@+EN?EJpY<#}BhrsUJC0afQFx7-pBeLXR9Mr+#w@!wSNR7vxHy@r`!9MFecB4O zh9jye3iSzL0@t3)OZ=OxFjjyK#KSF|zz@K}-+HaY6gW+O{T6%Zky@gD$6SW)Jq;V0 zt&LAG*YFO^+=ULohZZW*=3>7YgND-!$2}2)Mt~c>JO3j6QiPC-*ayH2xBF)2m7+}# z`@m#q{J9r~Dr^eBgrF(l^#sOjlVNFgDs5NR*Xp;V*wr~HqBx7?qBUZ8w)%vIbhhe) zt4(#1S~c$Cq7b_A%wpuah1Qn(X9#obljoY)VUoK%OiQZ#Fa|@ZvGD0_oxR=vz{>U* znC(W7HaUDTc5F!T77GswL-jj7e0#83DH2+lS-T@_^SaWfROz9btt*5zDGck${}*njAwf}3hLqKGLTeV&5(8FC+IP>s;p{L@a~RyCu)MIa zs~vA?_JQ1^2Xc&^cjDq02tT_Z0gkElR0Aa$v@VHi+5*)1(@&}gEXxP5Xon?lxE@is z9sxd|h#w2&P5uHJxWgmtVZJv5w>cl2ALzri;r57qg){6`urTu(2}EI?D?##g=!Sbh z*L*>c9xN1a3CH$u7C~u_!g81`W|xp=54oZl9CM)&V9~ATCC-Q!yfKD@vp#2EKh0(S zgt~aJ^oq-TM0IBol!w1S2j7tJ8H7;SR7yn4-H}iz&U^*zW95HrHiT!H&E|rSlnCYr z7Y1|V7xebn=TFbkH;>WIH6H>8;0?HS#b6lCke9rSsH%3AM1#2U-^*NVhXEIDSFtE^ z=jOo1>j!c__Bub(R*dHyGa)@3h?!ls1&M)d2{?W5#1|M@6|ENYYa`X=2EA_oJUw=I zjQ)K6;C!@>^i7vdf`pBOjH>Ts$97}B=lkb07<&;&?f#cy3I0p5{1=?O*#8m$C_5TE zh}&8lOWWF7I@|pRC$G2;Sm#IJfhKW@^jk=jfM1MdJP(v2fIrYTc{;e5;5gsp`}X8-!{9{S1{h+)<@?+D13s^B zq9(1Pu(Dfl#&z|~qJGuGSWDT&u{sq|huEsbJhiqMUae}K*g+R(vG7P$p6g}w*eYWn zQ7luPl1@{vX?PMK%-IBt+N7TMn~GB z!Ldy^(2Mp{fw_0;<$dgHAv1gZgyJAx%}dA?jR=NPW1K`FkoY zNDgag#YWI6-a2#&_E9NMIE~gQ+*)i<>0c)dSRUMHpg!+AL;a;^u|M1jp#0b<+#14z z+#LuQ1jCyV_GNj#lHWG3e9P@H34~n0VgP#(SBX=v|RSuOiY>L87 z#KA{JDDj2EOBX^{`a;xQxHtY1?q5^B5?up1akjEPhi1-KUsK|J9XEBAbt%^F`t0I- zjRYYKI4OB7Zq3FqJFBZwbI=RuT~J|4tA8x)(v2yB^^+TYYJS>Et`_&yge##PuQ%0I z^|X!Vtof}`UuIxPjoH8kofw4u1pT5h`Ip}d8;l>WcG^qTe>@x63s#zoJiGmDM@_h= zo;8IZR`@AJRLnBNtatipUvL^(1P_a;q8P%&voqy#R!0(bNBTlV&*W9QU?kRV1B*~I zWvI?SNo2cB<7bgVY{F_CF$7z!02Qxfw-Ew#p!8PC#! z1sRfOl`d-Y@&=)l(Sl4CS=>fVvor5lYm61C!!iF3NMocKQHUYr0%QM}a4v2>rzPfM zUO}YRDb7-NEqW+p_;e0{Zi%0C$&B3CKx6|4BW`@`AwsxE?Vu}@Jm<3%T5O&05z+Yq zkK!QF(vlN}Rm}m_J+*W4`8i~R&`P0&5!;^@S#>7qkfb9wxFv@(wN@$k%2*sEwen$a zQnWymf+#Uyv)0lQVd?L1gpS}jMQZ(NHHCKRyu zjK|Zai0|N_)5iv)67(zDBCK4Ktm#ygP|0(m5tU`*AzR&{TSeSY8W=v5^=Ic`ahxM-LBWO+uoL~wxZmgcSJMUF9q%<%>jsvh9Dnp^_e>J_V=ySx4p?SF0Y zg4ZpZt@!h>WR76~P3_YchYOak7oOzR|`t+h!BbN}?zd zq+vMTt0!duALNWDwWVIA$O=%{lWJEj;5(QD()huhFL5=6x_=1h|5ESMW&S|*oxgF# z-0GRIb ziolwI13hJ-Rl(4Rj@*^=&Zz3vD$RX8bFWvBM{niz(%?z0gWNh_vUvpBDoa>-N=P4c zbw-XEJ@txIbc<`wC883;&yE4ayVh>+N($SJ01m}fumz!#!aOg*;y4Hl{V{b;&ux3& zBEmSq2jQ7#IbVm3TPBw?2vVN z0wzj|Y6EBS(V%Pb+@OPkMvEKHW~%DZk#u|A18pZMmCrjWh%7J4Ph>vG61 zRBgJ6w^8dNRg2*=K$Wvh$t>$Q^SMaIX*UpBG)0bqcvY%*by=$EfZAy{ZOA#^tB(D( zh}T(SZgdTj?bG9u+G{Avs5Yr1x=f3k7%K|eJp^>BHK#~dsG<&+=`mM@>kQ-cAJ2k) zT+Ht5liXdc^(aMi9su~{pJUhe)!^U&qn%mV6PS%lye+Iw5F@Xv8E zdR4#?iz+R4--iiHDQmQWfNre=iofAbF~1oGTa1Ce?hId~W^kPuN(5vhNx++ZLkn?l zUA7L~{0x|qA%%%P=8+-Ck{&2$UHn#OQncFS@uUVuE39c9o~#hl)v#!$X(X*4ban2c z{buYr9!`H2;6n73n^W3Vg(!gdBV7$e#v3qubWALaUEAf@`ava{UTx%2~VVQbEE(*Q8_ zv#me9i+0=QnY)$IT+@3vP1l9Wrne+MlZNGO6|zUVG+v&lm7Xw3P*+gS6e#6mVx~(w zyuaXogGTw4!!&P3oZ1|4oc_sGEa&m3Jsqy^lzUdJ^y8RlvUjDmbC^NZ0AmO-c*&m( zSI%4P9f|s!B#073b>Eet`T@J;3qY!NrABuUaED6M^=s-Q^2oZS`jVzuA z>g&g$!Tc>`u-Q9PmKu0SLu-X(tZeZ<%7F+$j3qOOftaoXO5=4!+P!%Cx0rNU+@E~{ zxCclYb~G(Ci%o{}4PC(Bu>TyX9slm5A^2Yi$$kCq-M#Jl)a2W9L-bq5%@Pw^ zh*iuuAz`x6N_rJ1LZ7J^MU9~}RYh+EVIVP+-62u+7IC%1p@;xmmQ`dGCx$QpnIUtK z0`++;Ddz7{_R^~KDh%_yo8WM$IQhcNOALCIGC$3_PtUs?Y44@Osw;OZ()Lk=(H&Vc zXjkHt+^1@M|J%Q&?4>;%T-i%#h|Tb1u;pO5rKst8(Cv2!3U{TRXdm&>fWTJG)n*q&wQPjRzg%pS1RO9}U0*C6fhUi&f#qoV`1{U<&mWKS<$oVFW>{&*$6)r6Rx)F4W zdUL8Mm_qNk6ycFVkI5F?V+cYFUch$92|8O^-Z1JC94GU+Nuk zA#n3Z1q4<6zRiv%W5`NGk*Ym{#0E~IA6*)H-=RmfWIY%mEC0? zSih7uchi`9-WkF2@z1ev6J_N~u;d$QfSNLMgPVpHZoh9oH-8D*;EhoCr~*kJ<|-VD z_jklPveOxWZq40E!SV@0XXy+~Vfn!7nZ1GXsn~U$>#u0d*f?RL9!NMlz^qxYmz|xt zz6A&MUAV#eD%^GcP#@5}QH5e7AV`}(N2#(3xpc!7dDmgu7C3TpgX5Z|$%Vu8=&SQI zdxUk*XS-#C^-cM*O>k}WD5K81e2ayyRA)R&5>KT1QL!T!%@}fw{>BsF+-pzu>;7{g z^CCSWfH;YtJGT@+An0Ded#zM9>UEFOdR_Xq zS~!5R*{p1Whq62ynHo|n$4p7&d|bal{iGsxAY?opi3R${)Zt*8YyOU!$TWMYXF?|i zPXYr}wJp#EH;keSG5WYJ*(~oiu#GDR>C4%-HpIWr7v`W`lzQN-lb?*vpoit z8FqJ)`LC4w8fO8Fu}AYV`awF2NLMS4$f+?=KisU4P6@#+_t)5WDz@f*qE|NG0*hwO z&gv^k^kC6Fg;5>Gr`Q46C{6>3F(p0QukG6NM07rxa&?)_C*eyU(jtli>9Zh#eUb(y zt9NbC-bp0>^m?i`?$aJUyBmF`N0zQ% zvF_;vLVI{tq%Ji%u*8s2p4iBirv*uD(?t~PEz$CfxVa=@R z^HQu6-+I9w>a35kX!P)TfnJDD!)j8!%38(vWNe9vK0{k*`FS$ABZ`rdwfQe@IGDki zssfXnsa6teKXCZUTd^qhhhUZ}>GG_>F0~LG7*<*x;8e39nb-0Bka(l)%+QZ_IVy3q zcmm2uKO0p)9|HGxk*e_$mX2?->&-MXe`=Fz3FRTFfM!$_y}G?{F9jmNgD+L%R`jM1 zIP-kb=3Hlsb35Q&qo(%Ja(LwQj>~!GI|Hgq65J9^A!ibChYB3kxLn@&=#pr}BwON0Q=e5;#sF8GGGuzx6O}z%u3l?jlKF&8Y#lUA)Cs6ZiW8DgOk|q z=YBPAMsO7AoAhWgnSKae2I7%7*Xk>#AyLX-InyBO?OD_^2^nI4#;G|tBvg3C0ldO0 z*`$g(q^es4VqXH2t~0-u^m5cfK8eECh3Rb2h1kW%%^8A!+ya3OHLw$8kHorx4(vJO zAlVu$nC>D{7i?7xDg3116Y2e+)Zb4FPAdZaX}qA!WW{$d?u+sK(iIKqOE-YM zH7y^hkny24==(1;qEacfFU{W{xSXhffC&DJV&oqw`u~WAl@=HIel>KC-mLs2ggFld zsSm-03=Jd^XNDA4i$vKqJ|e|TBc19bglw{)QL${Q(xlN?E;lPumO~;4w_McND6d+R zsc2p*&uRWd`wTDszTcWKiii1mNBrF7n&LQp$2Z<}zkv=8k2s6-^+#siy_K1`5R+n( z++5VOU^LDo(kt3ok?@$3drI`<%+SWcF*`CUWqAJxl3PAq!X|q{al;8%HfgxxM#2Vb zeBS756iU|BzB>bN2NP=AX&!{uZXS;|F`LLd9F^97UTMnNks_t7EPnjZF`2ocD2*u+ z?oKP{xXrD*AKGYGkZtlnvCuazg6g16ZAF{Nu%w+LCZ+v_*`0R$NK)tOh_c#cze;o$ z)kY(eZ5Viv<5zl1XfL(#GO|2FlXL#w3T?hpj3BZ&OAl^L!7@ zy;+iJWYQYP?$(`li_!|bfn!h~k#=v-#XXyjTLd+_txOqZZETqSEp>m+O0ji7MxZ*W zSdq+yqEmafrsLErZG8&;kH2kbCwluSa<@1yU3^Q#5HmW(hYVR0E6!4ZvH;Cr<$`qf zSvqRc`Pq_9b+xrtN3qLmds9;d7HdtlR!2NV$rZPCh6>(7f7M}>C^LeM_5^b$B~mn| z#)?`E=zeo9(9?{O_ko>51~h|c?8{F=2=_-o(-eRc z9p)o51krhCmff^U2oUi#$AG2p-*wSq8DZ(i!Jmu1wzD*)#%J&r)yZTq`3e|v4>EI- z=c|^$Qhv}lEyG@!{G~@}Wbx~vxTxwKoe9zn%5_Z^H$F1?JG_Kadc(G8#|@yaf2-4< zM1bdQF$b5R!W1f`j(S>Id;CHMzfpyjYEC_95VQ*$U3y5piVy=9Rdwg7g&)%#6;U%b2W}_VVdh}qPnM4FY9zFP(5eR zWuCEFox6e;COjs$1RV}IbpE0EV;}5IP}Oq|zcb*77PEDIZU{;@_;8*22{~JRvG~1t zc+ln^I+)Q*+Ha>(@=ra&L&a-kD;l$WEN;YL0q^GE8+})U_A_StHjX_gO{)N>tx4&F zRK?99!6JqktfeS-IsD@74yuq*aFJoV{5&K(W`6Oa2Qy0O5JG>O`zZ-p7vBGh!MxS;}}h6(96Wp`dci3DY?|B@1p8fVsDf$|0S zfE{WL5g3<9&{~yygYyR?jK!>;eZ2L#tpL2)H#89*b zycE?VViXbH7M}m33{#tI69PUPD=r)EVPTBku={Qh{ zKi*pht1jJ+yRhVE)1=Y()iS9j`FesMo$bjLSqPMF-i<42Hxl6%y7{#vw5YT(C}x0? z$rJU7fFmoiR&%b|Y*pG?7O&+Jb#Z%S8&%o~fc?S9c`Dwdnc4BJC7njo7?3bp#Yonz zPC>y`DVK~nzN^n}jB5RhE4N>LzhCZD#WQseohYXvqp5^%Ns!q^B z&8zQN(jgPS(2ty~g2t9!x9;Dao~lYVujG-QEq{vZp<1Nlp;oj#kFVsBnJssU^p-4% zKF_A?5sRmA>d*~^og-I95z$>T*K*33TGBPzs{OMoV2i+(P6K|95UwSj$Zn<@Rt(g%|iY z$SkSjYVJ)I<@S(kMQ6md{HxAa8S`^lXGV?ktLX!ngTVI~%WW+p#A#XTWaFWeBAl%U z&rVhve#Yse*h4BC4nrq7A1n>Rlf^ErbOceJC`o#fyCu@H;y)`E#a#)w)3eg^{Hw&E7);N5*6V+z%olvLj zp^aJ4`h*4L4ij)K+uYvdpil(Z{EO@u{BcMI&}5{ephilI%zCkBhBMCvOQT#zp|!18 zuNl=idd81|{FpGkt%ty=$fnZnWXxem!t4x{ zat@68CPmac(xYaOIeF}@O1j8O?2jbR!KkMSuix;L8x?m01}|bS2=&gsjg^t2O|+0{ zlzfu5r5_l4)py8uPb5~NHPG>!lYVynw;;T-gk1Pl6PQ39Mwgd2O+iHDB397H)2grN zHwbd>8i%GY>Pfy7;y5X7AN>qGLZVH>N_ZuJZ-`z9UA> zfyb$nbmPqxyF2F;UW}7`Cu>SS%0W6h^Wq5e{PWAjxlh=#Fq+6SiPa-L*551SZKX&w zc9TkPv4eao?kqomkZ#X%tA{`UIvf|_=Y7p~mHZKqO>i_;q4PrwVtUDTk?M7NCssa?Y4uxYrsXj!+k@`Cxl;&{NLs*6!R<6k9$Bq z%grLhxJ#G_j~ytJpiND8neLfvD0+xu>wa$-%5v;4;RYYM66PUab)c9ruUm%d{^s{# zTBBY??@^foRv9H}iEf{w_J%rV<%T1wv^`)Jm#snLTIifjgRkX``x2wV(D6(=VTLL4 zI-o}&5WuwBl~(XSLIn5~{cGWorl#z+=(vXuBXC#lp}SdW=_)~8Z(Vv!#3h2@pdA3d z{cIPYK@Ojc9(ph=H3T7;aY>(S3~iuIn05Puh^32WObj%hVN(Y{Ty?n?Cm#!kGNZFa zW6Ybz!tq|@erhtMo4xAus|H8V_c+XfE5mu|lYe|{$V3mKnb1~fqoFim;&_ZHN_=?t zysQwC4qO}rTi}k8_f=R&i27RdBB)@bTeV9Wcd}Rysvod}7I%ujwYbTI*cN7Kbp_hO z=eU521!#cx$0O@k9b$;pnCTRtLIzv){nVW6Ux1<0@te6`S5%Ew3{Z^9=lbL5$NFvd4eUtK?%zgmB;_I&p`)YtpN`2Im(?jPN<(7Ua_ZWJRF(CChv`(gHfWodK%+joy>8Vaa;H1w zIJ?!kA|x7V;4U1BNr(UrhfvjPii7YENLIm`LtnL9Sx z5E9TYaILoB2nSwDe|BVmrpLT43*dJ8;T@1l zJE)4LEzIE{IN}+Nvpo3=ZtV!U#D;rB@9OXYw^4QH+(52&pQEcZq&~u9bTg63ikW9! z=!_RjN2xO=F+bk>fSPhsjQA;)%M1My#34T`I7tUf>Q_L>DRa=>Eo(sapm>}}LUsN% zVw!C~a)xcca`G#g*Xqo>_uCJTz>LoWGSKOwp-tv`yvfqw{17t`9Z}U4o+q2JGP^&9 z(m}|d13XhYSnEm$_8vH-Lq$A^>oWUz1)bnv|AVn_0FwM$vYu&8+qUg$+qP}nwrykD zwmIF?wr$()X@33oz1@B9zi+?Th^nZnsES)rb@O*K^JL~ZH|pRRk$i0+ohh?Il)y&~ zQaq{}9YxPt5~_2|+r#{k#~SUhO6yFq)uBGtYMMg4h1qddg!`TGHocYROyNFJtYjNe z3oezNpq6%TP5V1g(?^5DMeKV|i6vdBq)aGJ)BRv;K(EL0_q7$h@s?BV$)w31*c(jd z{@hDGl3QdXxS=#?0y3KmPd4JL(q(>0ikTk6nt98ptq$6_M|qrPi)N>HY>wKFbnCKY z%0`~`9p)MDESQJ#A`_>@iL7qOCmCJ(p^>f+zqaMuDRk!z01Nd2A_W^D%~M73jTqC* zKu8u$$r({vP~TE8rPk?8RSjlRvG*BLF}ye~Su%s~rivmjg2F z24dhh6-1EQF(c>Z1E8DWY)Jw#9U#wR<@6J)3hjA&2qN$X%piJ4s={|>d-|Gzl~RNu z##iR(m;9TN3|zh+>HgTI&82iR>$YVoOq$a(2%l*2mNP(AsV=lR^>=tIP-R9Tw!BYnZROx`PN*JiNH>8bG}&@h0_v$yOTk#@1;Mh;-={ZU7e@JE(~@@y0AuETvsqQV@7hbKe2wiWk@QvV=Kz`%@$rN z_0Hadkl?7oEdp5eaaMqBm;#Xj^`fxNO^GQ9S3|Fb#%{lN;1b`~yxLGEcy8~!cz{!! z=7tS!I)Qq%w(t9sTSMWNhoV#f=l5+a{a=}--?S!rA0w}QF!_Eq>V4NbmYKV&^OndM z4WiLbqeC5+P@g_!_rs01AY6HwF7)$~%Ok^(NPD9I@fn5I?f$(rcOQjP+z?_|V0DiN zb}l0fy*el9E3Q7fVRKw$EIlb&T0fG~fDJZL7Qn8*a5{)vUblM)*)NTLf1ll$ zpQ^(0pkSTol`|t~`Y4wzl;%NRn>689mpQrW=SJ*rB;7}w zVHB?&sVa2%-q@ANA~v)FXb`?Nz8M1rHKiZB4xC9<{Q3T!XaS#fEk=sXI4IFMnlRqG+yaFw< zF{}7tcMjV04!-_FFD8(FtuOZx+|CjF@-xl6-{qSFF!r7L3yD()=*Ss6fT?lDhy(h$ zt#%F575$U(3-e2LsJd>ksuUZZ%=c}2dWvu8f!V%>z3gajZ!Dlk zm=0|(wKY`c?r$|pX6XVo6padb9{EH}px)jIsdHoqG^(XH(7}r^bRa8BC(%M+wtcB? z6G2%tui|Tx6C3*#RFgNZi9emm*v~txI}~xV4C`Ns)qEoczZ>j*r zqQCa5k90Gntl?EX!{iWh=1t$~jVoXjs&*jKu0Ay`^k)hC^v_y0xU~brMZ6PPcmt5$ z@_h`f#qnI$6BD(`#IR0PrITIV^~O{uo=)+Bi$oHA$G* zH0a^PRoeYD3jU_k%!rTFh)v#@cq`P3_y=6D(M~GBud;4 zCk$LuxPgJ5=8OEDlnU!R^4QDM4jGni}~C zy;t2E%Qy;A^bz_5HSb5pq{x{g59U!ReE?6ULOw58DJcJy;H?g*ofr(X7+8wF;*3{rx>j&27Syl6A~{|w{pHb zeFgu0E>OC81~6a9(2F13r7NZDGdQxR8T68&t`-BK zE>ZV0*0Ba9HkF_(AwfAds-r=|dA&p`G&B_zn5f9Zfrz9n#Rvso`x%u~SwE4SzYj!G zVQ0@jrLwbYP=awX$21Aq!I%M{x?|C`narFWhp4n;=>Sj!0_J!k7|A0;N4!+z%Oqlk z1>l=MHhw3bi1vT}1!}zR=6JOIYSm==qEN#7_fVsht?7SFCj=*2+Ro}B4}HR=D%%)F z?eHy=I#Qx(vvx)@Fc3?MT_@D))w@oOCRR5zRw7614#?(-nC?RH`r(bb{Zzn+VV0bm zJ93!(bfrDH;^p=IZkCH73f*GR8nDKoBo|!}($3^s*hV$c45Zu>6QCV(JhBW=3(Tpf z=4PT6@|s1Uz+U=zJXil3K(N6;ePhAJhCIo`%XDJYW@x#7Za);~`ANTvi$N4(Fy!K- z?CQ3KeEK64F0@ykv$-0oWCWhYI-5ZC1pDqui@B|+LVJmU`WJ=&C|{I_))TlREOc4* zSd%N=pJ_5$G5d^3XK+yj2UZasg2) zXMLtMp<5XWWfh-o@ywb*nCnGdK{&S{YI54Wh2|h}yZ})+NCM;~i9H@1GMCgYf`d5n zwOR(*EEkE4-V#R2+Rc>@cAEho+GAS2L!tzisLl${42Y=A7v}h;#@71_Gh2MV=hPr0_a% z0!={Fcv5^GwuEU^5rD|sP;+y<%5o9;#m>ssbtVR2g<420(I-@fSqfBVMv z?`>61-^q;M(b3r2z{=QxSjyH=-%99fpvb}8z}d;%_8$$J$qJg1Sp3KzlO_!nCn|g8 zzg8skdHNsfgkf8A7PWs;YBz_S$S%!hWQ@G>guCgS--P!!Ui9#%GQ#Jh?s!U-4)7ozR?i>JXHU$| zg0^vuti{!=N|kWorZNFX`dJgdphgic#(8sOBHQdBkY}Qzp3V%T{DFb{nGPgS;QwnH9B9;-Xhy{? z(QVwtzkn9I)vHEmjY!T3ifk1l5B?%%TgP#;CqG-?16lTz;S_mHOzu#MY0w}XuF{lk z*dt`2?&plYn(B>FFXo+fd&CS3q^hquSLVEn6TMAZ6e*WC{Q2e&U7l|)*W;^4l~|Q= zt+yFlLVqPz!I40}NHv zE2t1meCuGH%<`5iJ(~8ji#VD{?uhP%F(TnG#uRZW-V}1=N%ev&+Gd4v!0(f`2Ar-Y z)GO6eYj7S{T_vxV?5^%l6TF{ygS_9e2DXT>9caP~xq*~oE<5KkngGtsv)sdCC zaQH#kSL%c*gLj6tV)zE6SGq|0iX*DPV|I`byc9kn_tNQkPU%y<`rj zMC}lD<93=Oj+D6Y2GNMZb|m$^)RVdi`&0*}mxNy0BW#0iq!GGN2BGx5I0LS>I|4op z(6^xWULBr=QRpbxIJDK~?h;K#>LwQI4N<8V?%3>9I5l+e*yG zFOZTIM0c3(q?y9f7qDHKX|%zsUF%2zN9jDa7%AK*qrI5@z~IruFP+IJy7!s~TE%V3 z_PSSxXlr!FU|Za>G_JL>DD3KVZ7u&}6VWbwWmSg?5;MabycEB)JT(eK8wg`^wvw!Q zH5h24_E$2cuib&9>Ue&@%Cly}6YZN-oO_ei5#33VvqV%L*~ZehqMe;)m;$9)$HBsM zfJ96Hk8GJyWwQ0$iiGjwhxGgQX$sN8ij%XJzW`pxqgwW=79hgMOMnC|0Q@ed%Y~=_ z?OnjUB|5rS+R$Q-p)vvM(eFS+Qr{_w$?#Y;0Iknw3u(+wA=2?gPyl~NyYa3me{-Su zhH#8;01jEm%r#5g5oy-f&F>VA5TE_9=a0aO4!|gJpu470WIrfGo~v}HkF91m6qEG2 zK4j=7C?wWUMG$kYbIp^+@)<#ArZ$3k^EQxraLk0qav9TynuE7T79%MsBxl3|nRn?L zD&8kt6*RJB6*a7=5c57wp!pg)p6O?WHQarI{o9@3a32zQ3FH8cK@P!DZ?CPN_LtmC6U4F zlv8T2?sau&+(i@EL6+tvP^&=|aq3@QgL4 zOu6S3wSWeYtgCnKqg*H4ifIQlR4hd^n{F+3>h3;u_q~qw-Sh;4dYtp^VYymX12$`? z;V2_NiRt82RC=yC+aG?=t&a81!gso$hQUb)LM2D4Z{)S zI1S9f020mSm(Dn$&Rlj0UX}H@ zv={G+fFC>Sad0~8yB%62V(NB4Z|b%6%Co8j!>D(VyAvjFBP%gB+`b*&KnJ zU8s}&F+?iFKE(AT913mq;57|)q?ZrA&8YD3Hw*$yhkm;p5G6PNiO3VdFlnH-&U#JH zEX+y>hB(4$R<6k|pt0?$?8l@zeWk&1Y5tlbgs3540F>A@@rfvY;KdnVncEh@N6Mfi zY)8tFRY~Z?Qw!{@{sE~vQy)0&fKsJpj?yR`Yj+H5SDO1PBId3~d!yjh>FcI#Ug|^M z7-%>aeyQhL8Zmj1!O0D7A2pZE-$>+-6m<#`QX8(n)Fg>}l404xFmPR~at%$(h$hYD zoTzbxo`O{S{E}s8Mv6WviXMP}(YPZoL11xfd>bggPx;#&pFd;*#Yx%TtN1cp)MuHf z+Z*5CG_AFPwk624V9@&aL0;=@Ql=2h6aJoqWx|hPQQzdF{e7|fe(m){0==hk_!$ou zI|p_?kzdO9&d^GBS1u+$>JE-6Ov*o{mu@MF-?$r9V>i%;>>Fo~U`ac2hD*X}-gx*v z1&;@ey`rA0qNcD9-5;3_K&jg|qvn@m^+t?8(GTF0l#|({Zwp^5Ywik@bW9mN+5`MU zJ#_Ju|jtsq{tv)xA zY$5SnHgHj}c%qlQG72VS_(OSv;H~1GLUAegygT3T-J{<#h}))pk$FjfRQ+Kr%`2ZiI)@$96Nivh82#K@t>ze^H?R8wHii6Pxy z0o#T(lh=V>ZD6EXf0U}sG~nQ1dFI`bx;vivBkYSVkxXn?yx1aGxbUiNBawMGad;6? zm{zp?xqAoogt=I2H0g@826=7z^DmTTLB11byYvAO;ir|O0xmNN3Ec0w%yHO({-%q(go%?_X{LP?=E1uXoQgrEGOfL1?~ zI%uPHC23dn-RC@UPs;mxq6cFr{UrgG@e3ONEL^SoxFm%kE^LBhe_D6+Ia+u0J=)BC zf8FB!0J$dYg33jb2SxfmkB|8qeN&De!%r5|@H@GiqReK(YEpnXC;-v~*o<#JmYuze zW}p-K=9?0=*fZyYTE7A}?QR6}m_vMPK!r~y*6%My)d;x4R?-=~MMLC_02KejX9q6= z4sUB4AD0+H4ulSYz4;6mL8uaD07eXFvpy*i5X@dmx--+9`ur@rcJ5<L#s%nq3MRi4Dpr;#28}dl36M{MkVs4+Fm3Pjo5qSV)h}i(2^$Ty|<7N z>*LiBzFKH30D!$@n^3B@HYI_V1?yM(G$2Ml{oZ}?frfPU+{i|dHQOP^M0N2#NN_$+ zs*E=MXUOd=$Z2F4jSA^XIW=?KN=w6{_vJ4f(ZYhLxvFtPozPJv9k%7+z!Zj+_0|HC zMU0(8`8c`Sa=%e$|Mu2+CT22Ifbac@7Vn*he`|6Bl81j`44IRcTu8aw_Y%;I$Hnyd zdWz~I!tkWuGZx4Yjof(?jM;exFlUsrj5qO=@2F;56&^gM9D^ZUQ!6TMMUw19zslEu zwB^^D&nG96Y+Qwbvgk?Zmkn9%d{+V;DGKmBE(yBWX6H#wbaAm&O1U^ zS4YS7j2!1LDC6|>cfdQa`}_^satOz6vc$BfFIG07LoU^IhVMS_u+N=|QCJao0{F>p z-^UkM)ODJW9#9*o;?LPCRV1y~k9B`&U)jbTdvuxG&2%!n_Z&udT=0mb@e;tZ$_l3bj6d0K2;Ya!&)q`A${SmdG_*4WfjubB)Mn+vaLV+)L5$yD zYSTGxpVok&fJDG9iS8#oMN{vQneO|W{Y_xL2Hhb%YhQJgq7j~X7?bcA|B||C?R=Eo z!z;=sSeKiw4mM$Qm>|aIP3nw36Tbh6Eml?hL#&PlR5xf9^vQGN6J8op1dpLfwFg}p zlqYx$610Zf?=vCbB_^~~(e4IMic7C}X(L6~AjDp^;|=d$`=!gd%iwCi5E9<6Y~z0! zX8p$qprEadiMgq>gZ_V~n$d~YUqqqsL#BE6t9ufXIUrs@DCTfGg^-Yh5Ms(wD1xAf zTX8g52V!jr9TlWLl+whcUDv?Rc~JmYs3haeG*UnV;4bI=;__i?OSk)bF3=c9;qTdP zeW1exJwD+;Q3yAw9j_42Zj9nuvs%qGF=6I@($2Ue(a9QGRMZTd4ZAlxbT5W~7(alP1u<^YY!c3B7QV z@jm$vn34XnA6Gh1I)NBgTmgmR=O1PKp#dT*mYDPRZ=}~X3B8}H*e_;;BHlr$FO}Eq zJ9oWk0y#h;N1~ho724x~d)A4Z-{V%F6#e5?Z^(`GGC}sYp5%DKnnB+i-NWxwL-CuF+^JWNl`t@VbXZ{K3#aIX+h9-{T*+t(b0BM&MymW9AA*{p^&-9 zWpWQ?*z(Yw!y%AoeoYS|E!(3IlLksr@?Z9Hqlig?Q4|cGe;0rg#FC}tXTmTNfpE}; z$sfUYEG@hLHUb$(K{A{R%~%6MQN|Bu949`f#H6YC*E(p3lBBKcx z-~Bsd6^QsKzB0)$FteBf*b3i7CN4hccSa-&lfQz4qHm>eC|_X!_E#?=`M(bZ{$cvU zZpMbr|4omp`s9mrgz@>4=Fk3~8Y7q$G{T@?oE0<(I91_t+U}xYlT{c&6}zPAE8ikT z3DP!l#>}i!A(eGT+@;fWdK#(~CTkwjs?*i4SJVBuNB2$6!bCRmcm6AnpHHvnN8G<| zuh4YCYC%5}Zo;BO1>L0hQ8p>}tRVx~O89!${_NXhT!HUoGj0}bLvL2)qRNt|g*q~B z7U&U7E+8Ixy1U`QT^&W@ZSRN|`_Ko$-Mk^^c%`YzhF(KY9l5))1jSyz$&>mWJHZzHt0Jje%BQFxEV}C00{|qo5_Hz7c!FlJ|T(JD^0*yjkDm zL}4S%JU(mBV|3G2jVWU>DX413;d+h0C3{g3v|U8cUj`tZL37Sf@1d*jpwt4^B)`bK zZdlwnPB6jfc7rIKsldW81$C$a9BukX%=V}yPnaBz|i6(h>S)+Bn44@i8RtBZf0XetH&kAb?iAL zD%Ge{>Jo3sy2hgrD?15PM}X_)(6$LV`&t*D`IP)m}bzM)+x-xRJ zavhA)>hu2cD;LUTvN38FEtB94ee|~lIvk~3MBPzmTsN|7V}Kzi!h&za#NyY zX^0BnB+lfBuW!oR#8G&S#Er2bCVtA@5FI`Q+a-e?G)LhzW_chWN-ZQmjtR

eWu-UOPu^G}|k=o=;ffg>8|Z*qev7qS&oqA7%Z{4Ezb!t$f3& z^NuT8CSNp`VHScyikB1YO{BgaBVJR&>dNIEEBwYkfOkWN;(I8CJ|vIfD}STN z{097)R9iC@6($s$#dsb*4BXBx7 zb{6S2O}QUk>upEfij9C2tjqWy7%%V@Xfpe)vo6}PG+hmuY1Tc}peynUJLLmm)8pshG zb}HWl^|sOPtYk)CD-7{L+l(=F zOp}fX8)|n{JDa&9uI!*@jh^^9qP&SbZ(xxDhR)y|bjnn|K3MeR3gl6xcvh9uqzb#K zYkVjnK$;lUky~??mcqN-)d5~mk{wXhrf^<)!Jjqc zG~hX0P_@KvOKwV=X9H&KR3GnP3U)DfqafBt$e10}iuVRFBXx@uBQ)sn0J%%c<;R+! zQz;ETTVa+ma>+VF%U43w?_F6s0=x@N2(oisjA7LUOM<$|6iE|$WcO67W|KY8JUV_# zg7P9K3Yo-c*;EmbsqT!M4(WT`%9uk+s9Em-yB0bE{B%F4X<8fT!%4??vezaJ(wJhj zfOb%wKfkY3RU}7^FRq`UEbB-#A-%7)NJQwQd1As=!$u#~2vQ*CE~qp`u=_kL<`{OL zk>753UqJVx1-4~+d@(pnX-i zV4&=eRWbJ)9YEGMV53poXpv$vd@^yd05z$$@i5J7%>gYKBx?mR2qGv&BPn!tE-_aW zg*C!Z&!B zH>3J16dTJC(@M0*kIc}Jn}jf=f*agba|!HVm|^@+7A?V>Woo!$SJko*Jv1mu>;d}z z^vF{3u5Mvo_94`4kq2&R2`32oyoWc2lJco3`Ls0Ew4E7*AdiMbn^LCV%7%mU)hr4S3UVJjDLUoIKRQ)gm?^{1Z}OYzd$1?a~tEY ztjXmIM*2_qC|OC{7V%430T?RsY?ZLN$w!bkDOQ0}wiq69){Kdu3SqW?NMC))S}zq^ zu)w!>E1!;OrXO!RmT?m&PA;YKUjJy5-Seu=@o;m4*Vp$0OipBl4~Ub)1xBdWkZ47=UkJd$`Z}O8ZbpGN$i_WtY^00`S8=EHG#Ff{&MU1L(^wYjTchB zMTK%1LZ(eLLP($0UR2JVLaL|C2~IFbWirNjp|^=Fl48~Sp9zNOCZ@t&;;^avfN(NpNfq}~VYA{q%yjHo4D>JB>XEv(~Z!`1~SoY=9v zTq;hrjObE_h)cmHXLJ>LC_&XQ2BgGfV}e#v}ZF}iF97bG`Nog&O+SA`2zsn%bbB309}I$ zYi;vW$k@fC^muYBL?XB#CBuhC&^H)F4E&vw(5Q^PF{7~}(b&lF4^%DQzL0(BVk?lM zTHXTo4?Ps|dRICEiux#y77_RF8?5!1D-*h5UY&gRY`WO|V`xxB{f{DHzBwvt1W==r zdfAUyd({^*>Y7lObr;_fO zxDDw7X^dO`n!PLqHZ`by0h#BJ-@bAFPs{yJQ~Ylj^M5zWsxO_WFHG}8hH>OK{Q)9` zSRP94d{AM(q-2x0yhK@aNMv!qGA5@~2tB;X?l{Pf?DM5Y*QK`{mGA? zjx;gwnR~#Nep12dFk<^@-U{`&`P1Z}Z3T2~m8^J&7y}GaMElsTXg|GqfF3>E#HG=j zMt;6hfbfjHSQ&pN9(AT8q$FLKXo`N(WNHDY!K6;JrHZCO&ISBdX`g8sXvIf?|8 zX$-W^ut!FhBxY|+R49o44IgWHt}$1BuE|6|kvn1OR#zhyrw}4H*~cpmFk%K(CTGYc zNkJ8L$eS;UYDa=ZHWZy`rO`!w0oIcgZnK&xC|93#nHvfb^n1xgxf{$LB`H1ao+OGb zKG_}>N-RHSqL(RBdlc7J-Z$Gaay`wEGJ_u-lo88{`aQ*+T~+x(H5j?Q{uRA~>2R+} zB+{wM2m?$->unwg8-GaFrG%ZmoHEceOj{W21)Mi2lAfT)EQuNVo+Do%nHPuq7Ttt7 z%^6J5Yo64dH671tOUrA7I2hL@HKZq;S#Ejxt;*m-l*pPj?=i`=E~FAXAb#QH+a}-% z#3u^pFlg%p{hGiIp>05T$RiE*V7bPXtkz(G<+^E}Risi6F!R~Mbf(Qz*<@2&F#vDr zaL#!8!&ughWxjA(o9xtK{BzzYwm_z2t*c>2jI)c0-xo8ahnEqZ&K;8uF*!Hg0?Gd* z=eJK`FkAr>7$_i$;kq3Ks5NNJkNBnw|1f-&Ys56c9Y@tdM3VTTuXOCbWqye9va6+ZSeF0eh} zYb^ct&4lQTfNZ3M3(9?{;s><(zq%hza7zcxlZ+`F8J*>%4wq8s$cC6Z=F@ zhbvdv;n$%vEI$B~B)Q&LkTse!8Vt};7Szv2@YB!_Ztp@JA>rc(#R1`EZcIdE+JiI% zC2!hgYt+~@%xU?;ir+g92W`*j z3`@S;I6@2rO28zqj&SWO^CvA5MeNEhBF+8-U0O0Q1Co=I^WvPl%#}UFDMBVl z5iXV@d|`QTa$>iw;m$^}6JeuW zjr;{)S2TfK0Q%xgHvONSJb#NA|LOmg{U=k;R?&1tQbylMEY4<1*9mJh&(qo`G#9{X zYRs)#*PtEHnO;PV0G~6G`ca%tpKgb6<@)xc^SQY58lTo*S$*sv5w7bG+8YLKYU`8{ zNBVlvgaDu7icvyf;N&%42z2L4(rR<*Jd48X8Jnw zN>!R$%MZ@~Xu9jH?$2Se&I|ZcW>!26BJP?H7og0hT(S`nXh6{sR36O^7%v=31T+eL z)~BeC)15v>1m#(LN>OEwYFG?TE0_z)MrT%3SkMBBjvCd6!uD+03Jz#!s#Y~b1jf>S z&Rz5&8rbLj5!Y;(Hx|UY(2aw~W(8!3q3D}LRE%XX(@h5TnP@PhDoLVQx;6|r^+Bvs zaR55cR%Db9hZ<<|I%dDkone+8Sq7dqPOMnGoHk~-R*#a8w$c)`>4U`k+o?2|E>Sd4 zZ0ZVT{95pY$qKJ54K}3JB!(WcES>F+x56oJBRg))tMJ^#Qc(2rVcd5add=Us6vpBNkIg9b#ulk%!XBU zV^fH1uY(rGIAiFew|z#MM!qsVv%ZNb#why9%9In4Kj-hDYtMdirWLFzn~de!nnH(V zv0>I3;X#N)bo1$dFzqo(tzmvqNUKraAz~?)OSv42MeM!OYu;2VKn2-s7#fucX`|l~ zplxtG1Pgk#(;V=`P_PZ`MV{Bt4$a7;aLvG@KQo%E=;7ZO&Ws-r@XL+AhnPn>PAKc7 zQ_iQ4mXa-a4)QS>cJzt_j;AjuVCp8g^|dIV=DI0>v-f_|w5YWAX61lNBjZEZax3aV znher(j)f+a9_s8n#|u=kj0(unR1P-*L7`{F28xv054|#DMh}q=@rs@-fbyf(2+52L zN>hn3v!I~%jfOV=j(@xLOsl$Jv-+yR5{3pX)$rIdDarl7(C3)})P`QoHN|y<<2n;` zJ0UrF=Zv}d=F(Uj}~Yv9(@1pqUSRa5_bB*AvQ|Z-6YZ*N%p(U z<;Bpqr9iEBe^LFF!t{1UnRtaH-9=@p35fMQJ~1^&)(2D|^&z?m z855r&diVS6}jmt2)A7LZDiv;&Ys6@W5P{JHY!!n7W zvj3(2{1R9Y=TJ|{^2DK&be*ZaMiRHw>WVI^701fC) zAp1?8?oiU%Faj?Qhou6S^d11_7@tEK-XQ~%q!!7hha-Im^>NcRF7OH7s{IO7arZQ{ zE8n?2><7*!*lH}~usWPWZ}2&M+)VQo7C!AWJSQc>8g_r-P`N&uybK5)p$5_o;+58Q z-Ux2l<3i|hxqqur*qAfHq=)?GDchq}ShV#m6&w|mi~ar~`EO_S=fb~<}66U>5i7$H#m~wR;L~4yHL2R&;L*u7-SPdHxLS&Iy76q$2j#Pe)$WulRiCICG*t+ zeehM8`!{**KRL{Q{8WCEFLXu3+`-XF(b?c1Z~wg?c0lD!21y?NLq?O$STk3NzmrHM zsCgQS5I+nxDH0iyU;KKjzS24GJmG?{D`08|N-v+Egy92lBku)fnAM<}tELA_U`)xKYb=pq|hejMCT1-rg0Edt6(*E9l9WCKI1a=@c99swp2t6Tx zFHy`8Hb#iXS(8c>F~({`NV@F4w0lu5X;MH6I$&|h*qfx{~DJ*h5e|61t1QP}tZEIcjC%!Fa)omJTfpX%aI+OD*Y(l|xc0$1Zip;4rx; zV=qI!5tSuXG7h?jLR)pBEx!B15HCoVycD&Z2dlqN*MFQDb!|yi0j~JciNC!>){~ zQQgmZvc}0l$XB0VIWdg&ShDTbTkArryp3x)T8%ulR;Z?6APx{JZyUm=LC-ACkFm`6 z(x7zm5ULIU-xGi*V6x|eF~CN`PUM%`!4S;Uv_J>b#&OT9IT=jx5#nydC4=0htcDme zDUH*Hk-`Jsa>&Z<7zJ{K4AZE1BVW%zk&MZ^lHyj8mWmk|Pq8WwHROz0Kwj-AFqvR)H2gDN*6dzVk>R3@_CV zw3Z@6s^73xW)XY->AFwUlk^4Q=hXE;ckW=|RcZFchyOM0vqBW{2l*QR#v^SZNnT6j zZv|?ZO1-C_wLWVuYORQryj29JA; zS4BsxfVl@X!W{!2GkG9fL4}58Srv{$-GYngg>JuHz!7ZPQbfIQr4@6ZC4T$`;Vr@t zD#-uJ8A!kSM*gA&^6yWi|F}&59^*Rx{qn3z{(JYxrzg!X2b#uGd>&O0e=0k_2*N?3 zYXV{v={ONL{rW~z_FtFj7kSSJZ?s);LL@W&aND7blR8rlvkAb48RwJZlOHA~t~RfC zOD%ZcOzhYEV&s9%qns0&ste5U!^MFWYn`Od()5RwIz6%@Ek+Pn`s79unJY-$7n-Uf z&eUYvtd)f7h7zG_hDiFC!psCg#q&0c=GHKOik~$$>$Fw*k z;G)HS$IR)Cu72HH|JjeeauX;U6IgZ_IfxFCE_bGPAU25$!j8Etsl0Rk@R`$jXuHo8 z3Hhj-rTR$Gq(x)4Tu6;6rHQhoCvL4Q+h0Y+@Zdt=KTb0~wj7-(Z9G%J+aQu05@k6JHeCC|YRFWGdDCV}ja;-yl^9<`>f=AwOqML1a~* z9@cQYb?!+Fmkf}9VQrL8$uyq8k(r8)#;##xG9lJ-B)Fg@15&To(@xgk9SP*bkHlxiy8I*wJQylh(+9X~H-Is!g&C!q*eIYuhl&fS&|w)dAzXBdGJ&Mp$+8D| zZaD<+RtjI90QT{R0YLk6_dm=GfCg>7;$ zlyLsNYf@MfLH<}ott5)t2CXiQos zFLt^`%ygB2Vy^I$W3J_Rt4olRn~Gh}AW(`F@LsUN{d$sR%bU&3;rsD=2KCL+4c`zv zlI%D>9-)U&R3;>d1Vdd5b{DeR!HXDm44Vq*u?`wziLLsFUEp4El;*S0;I~D#TgG0s zBXYZS{o|Hy0A?LVNS)V4c_CFwyYj-E#)4SQq9yaf`Y2Yhk7yHSdos~|fImZG5_3~~o<@jTOH@Mc7`*xn-aO5F zyFT-|LBsm(NbWkL^oB-Nd31djBaYebhIGXhsJyn~`SQ6_4>{fqIjRp#Vb|~+Qi}Mdz!Zsw= zz?5L%F{c{;Cv3Q8ab>dsHp)z`DEKHf%e9sT(aE6$az?A}3P`Lm(~W$8Jr=;d8#?dm_cmv>2673NqAOenze z=&QW`?TQAu5~LzFLJvaJ zaBU3mQFtl5z?4XQDBWNPaH4y)McRpX#$(3o5Nx@hVoOYOL&-P+gqS1cQ~J;~1roGH zVzi46?FaI@w-MJ0Y7BuAg*3;D%?<_OGsB3)c|^s3A{UoAOLP8scn`!5?MFa|^cTvq z#%bYG3m3UO9(sH@LyK9-LSnlVcm#5^NRs9BXFtRN9kBY2mPO|@b7K#IH{B{=0W06) zl|s#cIYcreZ5p3j>@Ly@35wr-q8z5f9=R42IsII=->1stLo@Q%VooDvg@*K(H@*5g zUPS&cM~k4oqp`S+qp^*nxzm^0mg3h8ppEHQ@cXyQ=YKV-6)FB*$KCa{POe2^EHr{J zOxcVd)s3Mzs8m`iV?MSp=qV59blW9$+$P+2;PZDRUD~sr*CQUr&EDiCSfH@wuHez+ z`d5p(r;I7D@8>nbZ&DVhT6qe+accH;<}q$8Nzz|d1twqW?UV%FMP4Y@NQ`3(+5*i8 zP9*yIMP7frrneG3M9 zf>GsjA!O#Bifr5np-H~9lR(>#9vhE6W-r`EjjeQ_wdWp+rt{{L5t5t(Ho|4O24@}4 z_^=_CkbI`3;~sXTnnsv=^b3J}`;IYyvb1gM>#J9{$l#Zd*W!;meMn&yXO7x`Epx_Y zm-1wlu~@Ii_7D}>%tzlXW;zQT=uQXSG@t$<#6-W*^vy7Vr2TCpnix@7!_|aNXEnN<-m?Oq;DpN*x6f>w za1Wa5entFEDtA0SD%iZv#3{wl-S`0{{i3a9cmgNW`!TH{J*~{@|5f%CKy@uk*8~af zt_d34U4y&3y9IZ5cXxLQ?(XjH5?q3Z0KxK~y!-CUyWG6{<)5lkhbox0HnV&7^zNBn zjc|?X!Y=63(Vg>#&Wx%=LUr5{i@~OdzT#?P8xu#P*I_?Jl7xM4dq)4vi}3Wj_c=XI zSbc)@Q2Et4=(nBDU{aD(F&*%Ix!53_^0`+nOFk)}*34#b0Egffld|t_RV91}S0m)0 zap{cQDWzW$geKzYMcDZDAw480!1e1!1Onpv9fK9Ov~sfi!~OeXb(FW)wKx335nNY! za6*~K{k~=pw`~3z!Uq%?MMzSl#s%rZM{gzB7nB*A83XIGyNbi|H8X>a5i?}Rs+z^; z2iXrmK4|eDOu@{MdS+?@(!-Ar4P4?H_yjTEMqm7`rbV4P275(-#TW##v#Dt14Yn9UB-Sg3`WmL0+H~N;iC`Mg%pBl?1AAOfZ&e; z*G=dR>=h_Mz@i;lrGpIOQwezI=S=R8#);d*;G8I(39ZZGIpWU)y?qew(t!j23B9fD z?Uo?-Gx3}6r8u1fUy!u)7LthD2(}boE#uhO&mKBau8W8`XV7vO>zb^ZVWiH-DOjl2 zf~^o1CYVU8eBdmpAB=T%i(=y}!@3N%G-*{BT_|f=egqtucEtjRJJhSf)tiBhpPDpgzOpG12UgvOFnab&16Zn^2ZHjs)pbd&W1jpx%%EXmE^ zdn#R73^BHp3w%&v!0~azw(Fg*TT*~5#dJw%-UdxX&^^(~V&C4hBpc+bPcLRZizWlc zjR;$4X3Sw*Rp4-o+a4$cUmrz05RucTNoXRINYG*DPpzM&;d1GNHFiyl(_x#wspacQ zL)wVFXz2Rh0k5i>?Ao5zEVzT)R(4Pjmjv5pzPrav{T(bgr|CM4jH1wDp6z*_jnN{V ziN56m1T)PBp1%`OCFYcJJ+T09`=&=Y$Z#!0l0J2sIuGQtAr>dLfq5S;{XGJzNk@a^ zk^eHlC4Gch`t+ue3RviiOlhz81CD9z~d|n5;A>AGtkZMUQ#f>5M14f2d}2 z8<*LNZvYVob!p9lbmb!0jt)xn6O&JS)`}7v}j+csS3e;&Awj zoNyjnqLzC(QQ;!jvEYUTy73t_%16p)qMb?ihbU{y$i?=a7@JJoXS!#CE#y}PGMK~3 zeeqqmo7G-W_S97s2eed^erB2qeh4P25)RO1>MH7ai5cZJTEevogLNii=oKG)0(&f` z&hh8cO{of0;6KiNWZ6q$cO(1)9r{`}Q&%p*O0W7N--sw3Us;)EJgB)6iSOg(9p_mc zRw{M^qf|?rs2wGPtjVKTOMAfQ+ZNNkb$Ok0;Pe=dNc7__TPCzw^H$5J0l4D z%p(_0w(oLmn0)YDwrcFsc*8q)J@ORBRoZ54GkJpxSvnagp|8H5sxB|ZKirp%_mQt_ z81+*Y8{0Oy!r8Gmih48VuRPwoO$dDW@h53$C)duL4_(osryhwZSj%~KsZ?2n?b`Z* z#C8aMdZxYmCWSM{mFNw1ov*W}Dl=%GQpp90qgZ{(T}GOS8#>sbiEU;zYvA?=wbD5g+ahbd1#s`=| zV6&f#ofJC261~Ua6>0M$w?V1j##jh-lBJ2vQ%&z`7pO%frhLP-1l)wMs=3Q&?oth1 zefkPr@3Z(&OL@~|<0X-)?!AdK)ShtFJ;84G2(izo3cCuKc{>`+aDoziL z6gLTL(=RYeD7x^FYA%sPXswOKhVa4i(S4>h&mLvS##6-H?w8q!B<8Alk>nQEwUG)SFXK zETfcTwi=R3!ck|hSM`|-^N3NWLav&UTO{a9=&Tuz-Kq963;XaRFq#-1R18fi^Gb-; zVO>Q{Oe<^b0WA!hkBi9iJp3`kGwacXX2CVQ0xQn@Y2OhrM%e4)Ea7Y*Df$dY2BpbL zv$kX}*#`R1uNA(7lk_FAk~{~9Z*Si5xd(WKQdD&I?8Y^cK|9H&huMU1I(251D7(LL z+){kRc=ALmD;#SH#YJ+|7EJL6e~w!D7_IrK5Q=1DCulUcN(3j`+D_a|GP}?KYx}V+ zx_vLTYCLb0C?h;e<{K0`)-|-qfM16y{mnfX(GGs2H-;-lRMXyb@kiY^D;i1haxoEk zsQ7C_o2wv?;3KS_0w^G5#Qgf*>u)3bT<3kGQL-z#YiN9QH7<(oDdNlSdeHD zQJN-U*_wJM_cU}1YOH=m>DW~{%MAPxL;gLdU6S5xLb$gJt#4c2KYaEaL8ORWf=^(l z-2`8^J;&YG@vb9em%s~QpU)gG@24BQD69;*y&-#0NBkxumqg#YYomd2tyo0NGCr8N z5<5-E%utH?Ixt!(Y4x>zIz4R^9SABVMpLl(>oXnBNWs8w&xygh_e4*I$y_cVm?W-^ ze!9mPy^vTLRclXRGf$>g%Y{(#Bbm2xxr_Mrsvd7ci|X|`qGe5=54Zt2Tb)N zlykxE&re1ny+O7g#`6e_zyjVjRi5!DeTvSJ9^BJqQ*ovJ%?dkaQl!8r{F`@KuDEJB3#ho5 zmT$A&L=?}gF+!YACb=%Y@}8{SnhaGCHRmmuAh{LxAn0sg#R6P_^cJ-9)+-{YU@<^- zlYnH&^;mLVYE+tyjFj4gaAPCD4CnwP75BBXA`O*H(ULnYD!7K14C!kGL_&hak)udZ zkQN8)EAh&9I|TY~F{Z6mBv7sz3?<^o(#(NXGL898S3yZPTaT|CzZpZ~pK~*9Zcf2F zgwuG)jy^OTZD`|wf&bEdq4Vt$ir-+qM7BosXvu`>W1;iFN7yTvcpN_#at)Q4n+(Jh zYX1A-24l9H5jgY?wdEbW{(6U1=Kc?Utren80bP`K?J0+v@{-RDA7Y8yJYafdI<7-I z_XA!xeh#R4N7>rJ_?(VECa6iWhMJ$qdK0Ms27xG&$gLAy(|SO7_M|AH`fIY)1FGDp zlsLwIDshDU;*n`dF@8vV;B4~jRFpiHrJhQ6TcEm%OjWTi+KmE7+X{19 z>e!sg0--lE2(S0tK}zD&ov-{6bMUc%dNFIn{2^vjXWlt>+uxw#d)T6HNk6MjsfN~4 zDlq#Jjp_!wn}$wfs!f8NX3Rk#9)Q6-jD;D9D=1{$`3?o~caZjXU*U32^JkJ$ZzJ_% zQWNfcImxb!AV1DRBq`-qTV@g1#BT>TlvktYOBviCY!13Bv?_hGYDK}MINVi;pg)V- z($Bx1Tj`c?1I3pYg+i_cvFtcQ$SV9%%9QBPg&8R~Ig$eL+xKZY!C=;M1|r)$&9J2x z;l^a*Ph+isNl*%y1T4SviuK1Nco_spQ25v5-}7u?T9zHB5~{-+W*y3p{yjn{1obqf zYL`J^Uz8zZZN8c4Dxy~)k3Ws)E5eYi+V2C!+7Sm0uu{xq)S8o{9uszFTnE>lPhY=5 zdke-B8_*KwWOd%tQs_zf0x9+YixHp+Qi_V$aYVc$P-1mg?2|_{BUr$6WtLdIX2FaF zGmPRTrdIz)DNE)j*_>b9E}sp*(1-16}u za`dgT`KtA3;+e~9{KV48RT=CGPaVt;>-35}%nlFUMK0y7nOjoYds7&Ft~#>0$^ciZ zM}!J5Mz{&|&lyG^bnmh?YtR z*Z5EfDxkrI{QS#Iq752aiA~V)DRlC*2jlA|nCU!@CJwxO#<=j6ssn;muv zhBT9~35VtwsoSLf*(7vl&{u7d_K_CSBMbzr zzyjt&V5O#8VswCRK3AvVbS7U5(KvTPyUc0BhQ}wy0z3LjcdqH8`6F3!`)b3(mOSxL z>i4f8xor(#V+&#ph~ycJMcj#qeehjxt=~Na>dx#Tcq6Xi4?BnDeu5WBBxt603*BY& zZ#;o1kv?qpZjwK-E{8r4v1@g*lwb|8w@oR3BTDcbiGKs)a>Fpxfzh&b ziQANuJ_tNHdx;a*JeCo^RkGC$(TXS;jnxk=dx++D8|dmPP<0@ z$wh#ZYI%Rx$NKe-)BlJzB*bot0ras3I%`#HTMDthGtM_G6u-(tSroGp1Lz+W1Y`$@ zP`9NK^|IHbBrJ#AL3!X*g3{arc@)nuqa{=*2y+DvSwE=f*{>z1HX(>V zNE$>bbc}_yAu4OVn;8LG^naq5HZY zh{Hec==MD+kJhy6t=Nro&+V)RqORK&ssAxioc7-L#UQuPi#3V2pzfh6Ar400@iuV5 z@r>+{-yOZ%XQhsSfw%;|a4}XHaloW#uGluLKux0II9S1W4w=X9J=(k&8KU()m}b{H zFtoD$u5JlGfpX^&SXHlp$J~wk|DL^YVNh2w(oZ~1*W156YRmenU;g=mI zw({B(QVo2JpJ?pJqu9vijk$Cn+%PSw&b4c@uU6vw)DjGm2WJKt!X}uZ43XYlDIz%& z=~RlgZpU-tu_rD`5!t?289PTyQ zZgAEp=zMK>RW9^~gyc*x%vG;l+c-V?}Bm;^{RpgbEnt_B!FqvnvSy)T=R zGa!5GACDk{9801o@j>L8IbKp#!*Td5@vgFKI4w!5?R{>@^hd8ax{l=vQnd2RDHopo zwA+qb2cu4Rx9^Bu1WNYT`a(g}=&&vT`&Sqn-irxzX_j1=tIE#li`Hn=ht4KQXp zzZj`JO+wojs0dRA#(bXBOFn**o+7rPY{bM9m<+UBF{orv$#yF8)AiOWfuas5Fo`CJ zqa;jAZU^!bh8sjE7fsoPn%Tw11+vufr;NMm3*zC=;jB{R49e~BDeMR+H6MGzDlcA^ zKg>JEL~6_6iaR4i`tSfUhkgPaLXZ<@L7poRF?dw_DzodYG{Gp7#24<}=18PBT}aY` z{)rrt`g}930jr3^RBQNA$j!vzTh#Mo1VL`QCA&US?;<2`P+xy8b9D_Hz>FGHC2r$m zW>S9ywTSdQI5hh%7^e`#r#2906T?))i59O(V^Rpxw42rCAu-+I3y#Pg6cm#&AX%dy ze=hv0cUMxxxh1NQEIYXR{IBM&Bk8FK3NZI3z+M>r@A$ocd*e%x-?W;M0pv50p+MVt zugo<@_ij*6RZ;IPtT_sOf2Zv}-3R_1=sW37GgaF9Ti(>V z1L4ju8RzM%&(B}JpnHSVSs2LH#_&@`4Kg1)>*)^i`9-^JiPE@=4l$+?NbAP?44hX&XAZy&?}1;=8c(e0#-3bltVWg6h=k!(mCx=6DqOJ-I!-(g;*f~DDe={{JGtH7=UY|0F zNk(YyXsGi;g%hB8x)QLpp;;`~4rx>zr3?A|W$>xj>^D~%CyzRctVqtiIz7O3pc@r@JdGJiH@%XR_9vaYoV?J3K1cT%g1xOYqhXfSa`fg=bCLy% zWG74UTdouXiH$?H()lyx6QXt}AS)cOa~3IdBxddcQp;(H-O}btpXR-iwZ5E)di9Jf zfToEu%bOR11xf=Knw7JovRJJ#xZDgAvhBDF<8mDu+Q|!}Z?m_=Oy%Ur4p<71cD@0OGZW+{-1QT?U%_PJJ8T!0d2*a9I2;%|A z9LrfBU!r9qh4=3Mm3nR_~X-EyNc<;?m`?dKUNetCnS)}_-%QcWuOpw zAdZF`4c_24z&m{H9-LIL`=Hrx%{IjrNZ~U<7k6p{_wRkR84g>`eUBOQd3x5 zT^kISYq)gGw?IB8(lu1=$#Vl?iZdrx$H0%NxW)?MO$MhRHn8$F^&mzfMCu>|`{)FL z`ZgOt`z%W~^&kzMAuWy9=q~$ldBftH0}T#(K5e8;j~!x$JjyspJ1IISI?ON5OIPB$ z-5_|YUMb+QUsiv3R%Ys4tVYW+x$}dg;hw%EdoH%SXMp`)v?cxR4wic{X9pVBH>=`#`Kcj!}x4 zV!`6tj|*q?jZdG(CSevn(}4Ogij5 z-kp;sZs}7oNu0x+NHs~(aWaKGV@l~TBkmW&mPj==N!f|1e1SndS6(rPxsn7dz$q_{ zL0jSrihO)1t?gh8N zosMjR3n#YC()CVKv zos2TbnL&)lHEIiYdz|%6N^vAUvTs6?s|~kwI4uXjc9fim`KCqW3D838Xu{48p$2?I zOeEqQe1}JUZECrZSO_m=2<$^rB#B6?nrFXFpi8jw)NmoKV^*Utg6i8aEW|^QNJuW& z4cbXpHSp4|7~TW(%JP%q9W2~@&@5Y5%cXL#fMhV59AGj<3$Hhtfa>24DLk{7GZUtr z5ql**-e58|mbz%5Kk~|f!;g+Ze^b);F+5~^jdoq#m+s?Y*+=d5ruym%-Tnn8htCV; zDyyUrWydgDNM&bI{yp<_wd-q&?Ig+BN-^JjWo6Zu3%Eov^Ja>%eKqrk&7kUqeM8PL zs5D}lTe_Yx;e=K`TDya!-u%y$)r*Cr4bSfN*eZk$XT(Lv2Y}qj&_UaiTevxs_=HXjnOuBpmT> zBg|ty8?|1rD1~Ev^6=C$L9%+RkmBSQxlnj3j$XN?%QBstXdx+Vl!N$f2Ey`i3p@!f zzqhI3jC(TZUx|sP%yValu^nzEV96o%*CljO>I_YKa8wMfc3$_L()k4PB6kglP@IT#wBd*3RITYADL}g+hlzLYxFmCt=_XWS}=jg8`RgJefB57z(2n&&q>m ze&F(YMmoRZW7sQ;cZgd(!A9>7mQ2d#!-?$%G8IQ0`p1|*L&P$GnU0i0^(S;Rua4v8 z_7Qhmv#@+kjS-M|($c*ZOo?V2PgT;GKJyP1REABlZhPyf!kR(0UA7Bww~R<7_u6#t z{XNbiKT&tjne(&=UDZ+gNxf&@9EV|fblS^gxNhI-DH;|`1!YNlMcC{d7I{u_E~cJOalFEzDY|I?S3kHtbrN&}R3k zK(Ph_Ty}*L3Et6$cUW`0}**BY@44KtwEy(jW@pAt`>g> z&8>-TmJiDwc;H%Ae%k6$ndZlfKruu1GocgZrLN=sYI52}_I%d)~ z6z40!%W4I6ch$CE2m>Dl3iwWIbcm27QNY#J!}3hqc&~(F8K{^gIT6E&L!APVaQhj^ zjTJEO&?**pivl^xqfD(rpLu;`Tm1MV+Wtd4u>X6u5V{Yp%)xH$k410o{pGoKdtY0t@GgqFN zO=!hTcYoa^dEPKvPX4ukgUTmR#q840gRMMi%{3kvh9gt(wK;Fniqu9A%BMsq?U&B5DFXC8t8FBN1&UIwS#=S zF(6^Eyn8T}p)4)yRvs2rCXZ{L?N6{hgE_dkH_HA#L3a0$@UMoBw6RE9h|k_rx~%rB zUqeEPL|!Pbp|up2Q=8AcUxflck(fPNJYP1OM_4I(bc24a**Qnd-@;Bkb^2z8Xv?;3yZp*| zoy9KhLo=;8n0rPdQ}yAoS8eb zAtG5QYB|~z@Z(Fxdu`LmoO>f&(JzsO|v0V?1HYsfMvF!3| zka=}6U13(l@$9&=1!CLTCMS~L01CMs@Abl4^Q^YgVgizWaJa%{7t)2sVcZg0mh7>d z(tN=$5$r?s={yA@IX~2ot9`ZGjUgVlul$IU4N}{ zIFBzY3O0;g$BZ#X|VjuTPKyw*|IJ+&pQ` z(NpzU`o=D86kZ3E5#!3Ry$#0AW!6wZe)_xZ8EPidvJ0f+MQJZ6|ZJ$CEV6;Yt{OJnL`dewc1k>AGbkK9Gf5BbB-fg? zgC4#CPYX+9%LLHg@=c;_Vai_~#ksI~)5|9k(W()g6ylc(wP2uSeJ$QLATtq%e#zpT zp^6Y)bV+e_pqIE7#-hURQhfQvIZpMUzD8&-t$esrKJ}4`ZhT|woYi>rP~y~LRf`*2!6 z6prDzJ~1VOlYhYAuBHcu9m>k_F>;N3rpLg>pr;{EDkeQPHfPv~woj$?UTF=txmaZy z?RrVthxVcqUM;X*(=UNg4(L|0d250Xk)6GF&DKD@r6{aZo;(}dnO5@CP7pMmdsI)- zeYH*@#+|)L8x7)@GNBu0Npyyh6r z^~!3$x&w8N)T;|LVgnwx1jHmZn{b2V zO|8s#F0NZhvux?0W9NH5;qZ?P_JtPW86)4J>AS{0F1S0d}=L2`{F z_y;o;17%{j4I)znptnB z%No1W>o}H2%?~CFo~0j?pzWk?dV4ayb!s{#>Yj`ZJ!H)xn}*Z_gFHy~JDis)?9-P=z4iOQg{26~n?dTms7)+F}? zcXvnHHnnbNTzc!$t+V}=<2L<7l(84v1I3b;-)F*Q?cwLNlgg{zi#iS)*rQ5AFWe&~ zWHPPGy{8wEC9JSL?qNVY76=es`bA{vUr~L7f9G@mP}2MNF0Qhv6Sgs`r_k!qRbSXK zv16Qqq`rFM9!4zCrCeiVS~P2e{Pw^A8I?p?NSVR{XfwlQo*wj|Ctqz4X-j+dU7eGkC(2y`(P?FM?P4gKki3Msw#fM6paBq#VNc>T2@``L{DlnnA-_*i10Kre&@-H!Z7gzn9pRF61?^^ z8dJ5kEeVKb%Bly}6NLV}<0(*eZM$QTLcH#+@iWS^>$Of_@Mu1JwM!>&3evymgY6>C_)sK+n|A5G6(3RJz0k>(z2uLdzXeTw)e4*g!h} zn*UvIx-Ozx<3rCF#C`khSv`Y-b&R4gX>d5osr$6jlq^8vi!M$QGx05pJZoY#RGr*J zsJmOhfodAzYQxv-MoU?m_|h^aEwgEHt5h_HMkHwtE+OA03(7{hm1V?AlYAS7G$u5n zO+6?51qo@aQK5#l6pM`kD5OmI28g!J2Z{5kNlSuKl=Yj3QZ|bvVHU}FlM+{QV=<=) z+b|%Q!R)FE z@ycDMSKV2?*XfcAc5@IOrSI&3&aR$|oAD8WNA6O;p~q-J@ll{x`jP<*eEpIYOYnT zer_t=dYw6a0avjQtKN&#n&(KJ5Kr$RXPOp1@Fq#0Of zTXQkq4qQxKWR>x#d{Hyh?6Y)U07;Q$?BTl7mx2bSPY_juXub1 z%-$)NKXzE<%}q>RX25*oeMVjiz&r_z;BrQV-(u>!U>C*OisXNU*UftsrH6vAhTEm@ zoKA`?fZL1sdd!+G@*NNvZa>}37u^x8^T>VH0_6Bx{3@x5NAg&55{2jUE-w3zCJNJi z^IlU=+DJz-9K&4c@7iKj(zlj@%V}27?vYmxo*;!jZVXJMeDg;5T!4Y1rxNV-e$WAu zkk6^Xao8HC=w2hpLvM(!xwo|~$eG6jJj39zyQHf)E+NPJlfspUhzRv&_qr8+Z1`DA zz`EV=A)d=;2&J;eypNx~q&Ir_7e_^xXg(L9>k=X4pxZ3y#-ch$^TN}i>X&uwF%75c(9cjO6`E5 z16vbMYb!lEIM?jxn)^+Ld8*hmEXR4a8TSfqwBg1(@^8$p&#@?iyGd}uhWTVS`Mlpa zGc+kV)K7DJwd46aco@=?iASsx?sDjbHoDVU9=+^tk46|Fxxey1u)_}c1j z^(`5~PU%og1LdSBE5x4N&5&%Nh$sy0oANXwUcGa>@CCMqP`4W$ZPSaykK|giiuMIw zu#j)&VRKWP55I(5K1^cog|iXgaK1Z%wm%T;;M3X`-`TTWaI}NtIZj;CS)S%S(h}qq zRFQ#{m4Qk$7;1i*0PC^|X1@a1pcMq1aiRSCHq+mnfj^FS{oxWs0McCN-lK4>SDp#` z7=Duh)kXC;lr1g3dqogzBBDg6>et<<>m>KO^|bI5X{+eMd^-$2xfoP*&e$vdQc7J% zmFO~OHf7aqlIvg%P`Gu|3n;lKjtRd@;;x#$>_xU(HpZos7?ShZlQSU)bY?qyQM3cHh5twS6^bF8NBKDnJgXHa)? zBYv=GjsZuYC2QFS+jc#uCsaEPEzLSJCL=}SIk9!*2Eo(V*SAUqKw#?um$mUIbqQQb zF1Nn(y?7;gP#@ws$W76>TuGcG=U_f6q2uJq?j#mv7g;llvqu{Yk~Mo>id)jMD7;T> zSB$1!g)QpIf*f}IgmV;!B+3u(ifW%xrD=`RKt*PDC?M5KI)DO`VXw(7X-OMLd3iVU z0CihUN(eNrY;m?vwK{55MU`p1;JDF=6ITN$+!q8W#`iIsN8;W7H?`htf%RS9Lh+KQ z_p_4?qO4#*`t+8l-N|kAKDcOt zoHsqz_oO&n?@4^Mr*4YrkDX44BeS*0zaA1j@*c}{$;jUxRXx1rq7z^*NX6d`DcQ}L z6*cN7e%`2#_J4z8=^GM6>%*i>>X^_0u9qn%0JTUo)c0zIz|7a`%_UnB)-I1cc+ z0}jAK0}jBl|6-2VT759oxBnf%-;7vs>7Mr}0h3^$0`5FAy}2h{ps5%RJA|^~6uCqg zxBMK5bQVD{Aduh1lu4)`Up*&( zCJQ>nafDb#MuhSZ5>YmD@|TcrNv~Q%!tca;tyy8Iy2vu2CeA+AsV^q*Wohg%69XYq zP0ppEDEYJ9>Se&X(v=U#ibxg()m=83pLc*|otbG;`CYZ z*YgsakGO$E$E_$|3bns7`m9ARe%myU3$DE;RoQ<6hR8e;%`pxO1{GXb$cCZl9lVnJ$(c` z``G?|PhXaz`>)rb7jm2#v7=(W?@ zjUhrNndRFMQ}%^^(-nmD&J>}9w@)>l;mhRr@$}|4ueOd?U9ZfO-oi%^n4{#V`i}#f zqh<@f^%~(MnS?Z0xsQI|Fghrby<&{FA+e4a>c(yxFL!Pi#?DW!!YI{OmR{xEC7T7k zS_g*9VWI}d0IvIXx*d5<7$5Vs=2^=ews4qZGmAVyC^9e;wxJ%BmB(F5*&!yyABCtLVGL@`qW>X9K zpv=W~+EszGef=am3LG+#yIq5oLXMnZ_dxSLQ_&bwjC^0e8qN@v!p?7mg02H<9`uaJ zy0GKA&YQV2CxynI3T&J*m!rf4@J*eo235*!cB1zEMQZ%h5>GBF;8r37K0h?@|E*0A zIHUg0y7zm(rFKvJS48W7RJwl!i~<6X2Zw+Fbm9ekev0M;#MS=Y5P(kq^(#q11zsvq zDIppe@xOMnsOIK+5BTFB=cWLalK#{3eE>&7fd11>l2=MpNKjsZT2kmG!jCQh`~Fu0 z9P0ab`$3!r`1yz8>_7DYsO|h$kIsMh__s*^KXv?Z1O8|~sEz?Y{+GDzze^GPjk$E$ zXbA-1gd77#=tn)YKU=;JE?}De0)WrT%H9s3`fn|%YibEdyZov3|MJ>QWS>290eCZj z58i<*>dC9=kz?s$sP_9kK1p>nV3qvbleExyq56|o+oQsb{ZVmuu1n~JG z0sUvo_i4fSM>xRs8rvG$*+~GZof}&ISxn(2JU*K{L<3+b{bBw{68H&Uiup@;fWWl5 zgB?IWMab0LkXK(Hz#yq>scZbd2%=B?DO~^q9tarlzZysN+g}n0+v);JhbjUT8AYrt z3?;0r%p9zLJv1r$%q&HKF@;3~0wVwO!U5m;J`Mm|`Nc^80sZd+Wj}21*SPoF82hCF zoK?Vw;4ioafdAkZxT1er-LLVi-*0`@2Ur&*!b?0U>R;no+S%)xoBuBxRw$?weN-u~tKE}8xb@7Gs%(aC;e1-LIlSfXDK(faFW)mnHdrLc3`F z6ZBsT^u0uVS&il=>YVX^*5`k!P4g1)2LQmz{?&dgf`7JrA4ZeE0sikL`k!Eb6r=g0 z{aCy_0I>fxSAXQYz3lw5G|ivg^L@(x-uch!AphH+d;E4`175`R0#b^)Zp>EM1Ks=zx6_261>!7 z{7F#a{Tl@Tpw9S`>7_i|PbScS-(dPJv9_0-FBP_aa@Gg^2IoKNZM~#=sW$SH3MJ|{ zsQy8F43lX7hYx<{v^Q9`2QsMzeen3cGpiTgzVp- z`aj3&Wv0(he1qKI!2jpGpO-i0Wpcz%vdn`2o9x&3;^nsZPt3c \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/sk89q-worldguard/gradlew.bat b/sk89q-worldguard/gradlew.bat new file mode 100644 index 000000000..9618d8d96 --- /dev/null +++ b/sk89q-worldguard/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sk89q-worldguard/settings.gradle.kts b/sk89q-worldguard/settings.gradle.kts new file mode 100644 index 000000000..ead83aeae --- /dev/null +++ b/sk89q-worldguard/settings.gradle.kts @@ -0,0 +1,7 @@ +rootProject.name = "worldguard" + +include("worldguard-libs") +include("worldguard-libs:core") +include("worldguard-core") +//include("worldguard-libs:bukkit") +include("worldguard-bukkit") diff --git a/sk89q-worldguard/worldguard-bukkit/build.gradle.kts b/sk89q-worldguard/worldguard-bukkit/build.gradle.kts new file mode 100644 index 000000000..ecaf23899 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/build.gradle.kts @@ -0,0 +1,78 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +plugins { + `java-library` +} + +applyPlatformAndCoreConfiguration() +applyShadowConfiguration() + +repositories { + maven { + name = "paper" + url = uri("https://papermc.io/repo/repository/maven-public/") + } + maven { + name = "bstats" + url = uri("https://repo.codemc.org/repository/maven-public") + } + maven { + name = "aikar-timings" + url = uri("https://repo.aikar.co/nexus/content/groups/aikar/") + } +} + +configurations { + compileClasspath.get().extendsFrom(create("shadeOnly")) +} + +dependencies { + "api"(project(":worldguard-core")) + "compileOnly"("io.papermc.paper:paper-api:1.17-R0.1-SNAPSHOT") + "runtimeOnly"("org.spigotmc:spigot-api:1.17-R0.1-SNAPSHOT") { + exclude("junit", "junit") + } + "api"("com.sk89q.worldedit:worldedit-bukkit:${Versions.WORLDEDIT}") { isTransitive = false } + "implementation"("com.google.guava:guava:${Versions.GUAVA}") + "compileOnly"("com.sk89q:commandbook:2.3") { isTransitive = false } + "shadeOnly"("io.papermc:paperlib:1.0.6") + "shadeOnly"("org.bstats:bstats-bukkit:2.1.0") + "shadeOnly"("co.aikar:minecraft-timings:1.0.4") +} + +tasks.named("processResources") { + val internalVersion = project.ext["internalVersion"] + inputs.property("internalVersion", internalVersion) + filesMatching("plugin.yml") { + expand("internalVersion" to internalVersion) + } +} + +tasks.named("jar") { + val projectVersion = project.version + inputs.property("projectVersion", projectVersion) + manifest { + attributes("Implementation-Version" to projectVersion) + } +} + +tasks.named("shadowJar") { + configurations = listOf(project.configurations["shadeOnly"], project.configurations["runtimeClasspath"]) + + dependencies { + include(dependency(":worldguard-core")) + relocate("org.bstats", "com.sk89q.worldguard.bukkit.bstats") { + include(dependency("org.bstats:")) + } + relocate ("io.papermc.lib", "com.sk89q.worldguard.bukkit.paperlib") { + include(dependency("io.papermc:paperlib")) + } + relocate ("co.aikar.timings.lib", "com.sk89q.worldguard.bukkit.timingslib") { + include(dependency("co.aikar:minecraft-timings")) + } + } +} + +tasks.named("assemble").configure { + dependsOn("shadowJar") +} diff --git a/sk89q-worldguard/worldguard-bukkit/contrib/blacklist_table.sql b/sk89q-worldguard/worldguard-bukkit/contrib/blacklist_table.sql new file mode 100644 index 000000000..a1336ba61 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/contrib/blacklist_table.sql @@ -0,0 +1,24 @@ +-- Blacklist table for MySQL. +-- You must still configure WorldGuard to use your database. +-- If you do not plan on using a database for logging blacklist events, +-- you do not need to do anything with this file. + +CREATE TABLE `blacklist_events` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `event` varchar(25) NOT NULL, + `world` varchar(32) NOT NULL, + `player` varchar(16) NOT NULL, + `x` int(11) NOT NULL, + `y` int(11) NOT NULL, + `z` int(11) NOT NULL, + `item` int(11) NOT NULL, + `time` int(11) NOT NULL, + `comment` varchar(255) NULL, + PRIMARY KEY (`id`) +); + +-- Required update if you have an older version of the table: + +ALTER TABLE `blacklist_events` ADD `comment` VARCHAR( 255 ) NULL + +ALTER TABLE `blacklist_events` ADD `world` VARCHAR( 32 ) NOT NULL \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/contrib/region_manual_update_20110325.sql b/sk89q-worldguard/worldguard-bukkit/contrib/region_manual_update_20110325.sql new file mode 100644 index 000000000..765fe82b0 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/contrib/region_manual_update_20110325.sql @@ -0,0 +1,114 @@ +-- +-- This file only needs to be run if you are using a very old version +-- of the region database (before 2011/03/25). +-- +-- Otherwise, WG knows how to update your tables automatically, as well +-- as set them up initially. +-- + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; + +ALTER TABLE `region_cuboid` DROP FOREIGN KEY `fk_region_cuboid_region` ; + +ALTER TABLE `region_flag` DROP FOREIGN KEY `fk_flags_region1` ; + +ALTER TABLE `region_groups` DROP FOREIGN KEY `fk_region_groups_region` ; + +ALTER TABLE `region_players` DROP FOREIGN KEY `fk_region_players_user` , DROP FOREIGN KEY `fk_region_players_region` ; + +ALTER TABLE `region_poly2d` DROP FOREIGN KEY `fk_region_poly2d_region` ; + +ALTER TABLE `region_poly2d_point` DROP FOREIGN KEY `fk_region_poly2d_point_region_poly2d` ; + +ALTER TABLE `group` COLLATE = utf8_bin ; + +ALTER TABLE `region_cuboid` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , CHANGE COLUMN `min_x` `min_x` BIGINT(20) NOT NULL AFTER `world_id` , CHANGE COLUMN `min_y` `min_y` BIGINT(20) NOT NULL AFTER `min_x` , CHANGE COLUMN `max_x` `max_x` BIGINT(20) NOT NULL AFTER `min_z` , CHANGE COLUMN `max_y` `max_y` BIGINT(20) NOT NULL AFTER `max_x` , + ADD CONSTRAINT `fk_region_cuboid_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP PRIMARY KEY +, ADD PRIMARY KEY (`region_id`, `world_id`) ; + +ALTER TABLE `region_flag` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , CHANGE COLUMN `flag` `flag` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + ADD CONSTRAINT `fk_flags_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP INDEX `fk_flags_region` +, ADD INDEX `fk_flags_region` (`region_id` ASC, `world_id` ASC) ; + +ALTER TABLE `region_groups` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , DROP FOREIGN KEY `fk_region_groups_group` ; + +ALTER TABLE `region_groups` + ADD CONSTRAINT `fk_region_groups_group` + FOREIGN KEY (`group_id` ) + REFERENCES `group` (`id` ) + ON DELETE CASCADE + ON UPDATE CASCADE, + ADD CONSTRAINT `fk_region_groups_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP PRIMARY KEY +, ADD PRIMARY KEY (`region_id`, `world_id`, `group_id`) ; + +ALTER TABLE `region_players` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , + ADD CONSTRAINT `fk_region_users_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE, + ADD CONSTRAINT `fk_region_users_user` + FOREIGN KEY (`user_id` ) + REFERENCES `user` (`id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP PRIMARY KEY +, ADD PRIMARY KEY (`region_id`, `world_id`, `user_id`) +, ADD INDEX `fk_region_users_user` (`user_id` ASC) +, DROP INDEX `fk_region_players_user` ; + +ALTER TABLE `region_poly2d` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , CHANGE COLUMN `min_y` `min_y` INT(11) NOT NULL AFTER `world_id` , + ADD CONSTRAINT `fk_region_poly2d_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP PRIMARY KEY +, ADD PRIMARY KEY (`region_id`, `world_id`) ; + +ALTER TABLE `region_poly2d_point` COLLATE = utf8_bin , ADD COLUMN `world_id` INT(10) UNSIGNED NOT NULL AFTER `region_id` , CHANGE COLUMN `x` `x` BIGINT(20) NOT NULL AFTER `world_id` , + ADD CONSTRAINT `fk_region_poly2d_point_region_poly2d` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `region_poly2d` (`region_id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE +, DROP INDEX `fk_region_poly2d_point_region_poly2d` +, ADD INDEX `fk_region_poly2d_point_region_poly2d` (`region_id` ASC, `world_id` ASC) ; + +ALTER TABLE `user` COLLATE = utf8_bin ; + +ALTER TABLE `world` COLLATE = utf8_bin ; + + +UPDATE `region_cuboid` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +UPDATE `region_flag` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +UPDATE `region_groups` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +UPDATE `region_players` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +UPDATE `region_poly2d` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +UPDATE `region_poly2d_point` AS c SET c.`world_id` = (SELECT p.`world_id` FROM `region` AS p WHERE p.`id` = c.`region_id`); + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitConfigurationManager.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitConfigurationManager.java new file mode 100644 index 000000000..506ece663 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitConfigurationManager.java @@ -0,0 +1,129 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.config.YamlConfigurationManager; +import com.sk89q.worldedit.util.report.Unreported; + +import java.io.File; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class BukkitConfigurationManager extends YamlConfigurationManager { + + @Unreported private WorldGuardPlugin plugin; + @Unreported private ConcurrentMap worlds = new ConcurrentHashMap<>(); + + private boolean hasCommandBookGodMode; + boolean extraStats; + boolean timedSessionHandlers; + + /** + * Construct the object. + * + * @param plugin The plugin instance + */ + public BukkitConfigurationManager(WorldGuardPlugin plugin) { + super(); + this.plugin = plugin; + } + + public Collection getWorldConfigs() { + return worlds.values(); + } + + @Override + public void load() { + super.load(); + this.extraStats = getConfig().getBoolean("custom-metrics-charts", true); + this.timedSessionHandlers = getConfig().getBoolean("extra-timings.session-handlers", true); + } + + @Override + public File getDataFolder() { + return plugin.getDataFolder(); + } + + @Override + public void copyDefaults() { + // Create the default configuration file + plugin.createDefaultConfiguration(new File(plugin.getDataFolder(), "config.yml"), "config.yml"); + } + + @Override + public void unload() { + worlds.clear(); + } + + @Override + public void postLoad() { + // Load configurations for each world + for (World world : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + get(world); + } + getConfig().save(); + } + + /** + * Get the configuration for a world. + * + * @param world The world to get the configuration for + * @return {@code world}'s configuration + */ + @Override + public BukkitWorldConfiguration get(World world) { + String worldName = world.getName(); + return get(worldName); + } + + public BukkitWorldConfiguration get(String worldName) { + BukkitWorldConfiguration config = worlds.get(worldName); + BukkitWorldConfiguration newConfig = null; + + while (config == null) { + if (newConfig == null) { + newConfig = new BukkitWorldConfiguration(plugin, worldName, this.getConfig()); + } + worlds.putIfAbsent(worldName, newConfig); + config = worlds.get(worldName); + } + + return config; + } + + public void updateCommandBookGodMode() { + try { + if (plugin.getServer().getPluginManager().isPluginEnabled("CommandBook")) { + Class.forName("com.sk89q.commandbook.GodComponent"); + hasCommandBookGodMode = true; + return; + } + } catch (ClassNotFoundException ignore) {} + hasCommandBookGodMode = false; + } + + public boolean hasCommandBookGodMode() { + return hasCommandBookGodMode; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitDebugHandler.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitDebugHandler.java new file mode 100644 index 000000000..67dd8ba7e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitDebugHandler.java @@ -0,0 +1,230 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.paste.ActorCallbackPaste; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.event.debug.CancelLogging; +import com.sk89q.worldguard.bukkit.event.debug.LoggingBlockBreakEvent; +import com.sk89q.worldguard.bukkit.event.debug.LoggingBlockPlaceEvent; +import com.sk89q.worldguard.bukkit.event.debug.LoggingEntityDamageByEntityEvent; +import com.sk89q.worldguard.bukkit.event.debug.LoggingPlayerInteractEvent; +import com.sk89q.worldguard.bukkit.util.report.CancelReport; +import com.sk89q.worldguard.internal.platform.DebugHandler; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.block.Action; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.util.BlockIterator; + +import java.util.logging.Logger; + +public class BukkitDebugHandler implements DebugHandler { + + private static final Logger log = Logger.getLogger(BukkitDebugHandler.class.getCanonicalName()); + private static final int MAX_TRACE_DISTANCE = 20; + + private final WorldGuardPlugin plugin; + + BukkitDebugHandler(WorldGuardPlugin plugin) { + this.plugin = plugin; + } + + /** + * Simulate an event and print its report. + * + * @param receiver The receiver of the messages + * @param target The target + * @param event The event + * @param stacktraceMode Whether stack traces should be generated and posted + * @param The type of event + */ + private void testEvent(CommandSender receiver, Player target, T event, boolean stacktraceMode) throws + CommandPermissionsException { + boolean isConsole = receiver instanceof ConsoleCommandSender; + + if (!receiver.equals(target)) { + if (!isConsole) { + log.info(receiver.getName() + " is simulating an event on " + target.getName()); + } + + target.sendMessage( + ChatColor.RED + "(Please ignore any messages that may immediately follow.)"); + } + + Bukkit.getPluginManager().callEvent(event); + int start = new Exception().getStackTrace().length; + CancelReport report = new CancelReport(event, event.getCancels(), start); + report.setDetectingPlugin(!stacktraceMode); + String result = report.toString(); + + if (stacktraceMode) { + receiver.sendMessage(ChatColor.GRAY + "The report was printed to console."); + log.info("Event report for " + receiver.getName() + ":\n\n" + result); + + plugin.checkPermission(receiver, "worldguard.debug.pastebin"); + ActorCallbackPaste.pastebin(WorldGuard.getInstance().getSupervisor(), plugin.wrapCommandSender(receiver), + result, "Event debugging report: %s.txt"); + } else { + receiver.sendMessage(result.replaceAll("(?m)^", ChatColor.AQUA.toString())); + + if (result.length() >= 500 && !isConsole) { + receiver.sendMessage(ChatColor.GRAY + "The report was also printed to console."); + log.info("Event report for " + receiver.getName() + ":\n\n" + result); + } + } + } + + /** + * Get the source of the test. + * + * @param sender The message sender + * @param target The provided target + * @param fromTarget Whether the source should be the target + * @return The source + * @throws CommandException Thrown if a condition is not met + */ + private Player getSource(CommandSender sender, Player target, boolean fromTarget) throws CommandException { + if (fromTarget) { + return target; + } else { + if (sender instanceof Player) { + return (Player) sender; + } else { + throw new CommandException( + "If this command is not to be used in-game, use -t to run the test from the viewpoint of the given player rather than yourself."); + } + } + } + + /** + * Find the first non-air block in a ray trace. + * + * @param sender The sender + * @param target The target + * @param fromTarget Whether the trace should originate from the target + * @return The block found + * @throws CommandException Throw on an incorrect parameter + */ + private Block traceBlock(CommandSender sender, Player target, boolean fromTarget) throws CommandException { + Player source = getSource(sender, target, fromTarget); + + BlockIterator it = new BlockIterator(source); + int i = 0; + while (it.hasNext() && i < MAX_TRACE_DISTANCE) { + Block block = it.next(); + if (block.getType() != Material.AIR) { + return block; + } + i++; + } + + throw new CommandException("Not currently looking at a block that is close enough."); + } + + /** + * Find the first nearby entity in a ray trace. + * + * @param sender The sender + * @param target The target + * @param fromTarget Whether the trace should originate from the target + * @return The entity found + * @throws CommandException Throw on an incorrect parameter + */ + private Entity traceEntity(CommandSender sender, Player target, boolean fromTarget) throws CommandException { + Player source = getSource(sender, target, fromTarget); + + BlockIterator it = new BlockIterator(source); + int i = 0; + while (it.hasNext() && i < MAX_TRACE_DISTANCE) { + Block block = it.next(); + + // A very in-accurate and slow search + Entity[] entities = block.getChunk().getEntities(); + for (Entity entity : entities) { + if (!entity.equals(target) && entity.getLocation().distanceSquared(block.getLocation()) < 10) { + return entity; + } + } + + i++; + } + + throw new CommandException("Not currently looking at an entity that is close enough."); + } + + @Override + public void testBreak(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException { + CommandSender bukkitSender = plugin.unwrapActor(sender); + Player bukkitTarget = BukkitAdapter.adapt(target); + + Block block = traceBlock(bukkitSender, bukkitTarget, fromTarget); + sender.print(TextComponent.of("Testing BLOCK BREAK at ", TextColor.AQUA).append(TextComponent.of(block.toString(), TextColor.DARK_AQUA))); + LoggingBlockBreakEvent event = new LoggingBlockBreakEvent(block, bukkitTarget); + testEvent(bukkitSender, bukkitTarget, event, stackTraceMode); + } + + @Override + public void testPlace(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException { + CommandSender bukkitSender = plugin.unwrapActor(sender); + Player bukkitTarget = BukkitAdapter.adapt(target); + + Block block = traceBlock(bukkitSender, bukkitTarget, fromTarget); + sender.print(TextComponent.of("Testing BLOCK PLACE at ", TextColor.AQUA).append(TextComponent.of(block.toString(), TextColor.DARK_AQUA))); + LoggingBlockPlaceEvent event = new LoggingBlockPlaceEvent(block, block.getState(), block.getRelative(BlockFace.DOWN), bukkitTarget.getItemInHand(), bukkitTarget, true); + testEvent(bukkitSender, bukkitTarget, event, stackTraceMode); + } + + @Override + public void testInteract(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException { + CommandSender bukkitSender = plugin.unwrapActor(sender); + Player bukkitTarget = BukkitAdapter.adapt(target); + + Block block = traceBlock(bukkitSender, bukkitTarget, fromTarget); + sender.print(TextComponent.of("Testing BLOCK INTERACT at ", TextColor.AQUA).append(TextComponent.of(block.toString(), TextColor.DARK_AQUA))); + LoggingPlayerInteractEvent event = new LoggingPlayerInteractEvent(bukkitTarget, Action.RIGHT_CLICK_BLOCK, bukkitTarget.getItemInHand(), block, BlockFace.SOUTH); + testEvent(bukkitSender, bukkitTarget, event, stackTraceMode); + } + + @Override + public void testDamage(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException { + CommandSender bukkitSender = plugin.unwrapActor(sender); + Player bukkitTarget = BukkitAdapter.adapt(target); + Entity entity = traceEntity(bukkitSender, bukkitTarget, fromTarget); + sender.print(TextComponent.of("Testing ENTITY DAMAGE at ", TextColor.AQUA).append(TextComponent.of(entity.toString(), TextColor.DARK_AQUA))); + LoggingEntityDamageByEntityEvent event = new LoggingEntityDamageByEntityEvent(bukkitTarget, entity, EntityDamageEvent.DamageCause.ENTITY_ATTACK, 1); + testEvent(bukkitSender, bukkitTarget, event, stackTraceMode); + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitOfflinePlayer.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitOfflinePlayer.java new file mode 100644 index 000000000..9818d0c78 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitOfflinePlayer.java @@ -0,0 +1,241 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.HandSide; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.weather.WeatherType; +import org.bukkit.OfflinePlayer; + +import java.util.UUID; + +import javax.annotation.Nullable; + +class BukkitOfflinePlayer extends BukkitPlayer { + + private final OfflinePlayer player; + + BukkitOfflinePlayer(WorldGuardPlugin plugin, OfflinePlayer offlinePlayer) { + super(plugin, offlinePlayer.getPlayer()); // null if they are offline + this.player = offlinePlayer; + } + + /// ======================================== + /// These are checked for RegionAssociable + /// (to see if a player belongs to a region) + /// ======================================== + + @Override + public String getName() { + return player.getName(); + } + + @Override + public UUID getUniqueId() { + return player.getUniqueId(); + } + + @Override + public boolean hasGroup(String group) { + return plugin.inGroup(player, group); + } + + @Override + public String[] getGroups() { + return plugin.getGroups(player); + } + + /// ========================================== + /// None of the following should ever be used. + /// ========================================== + + @Override + public boolean hasPermission(String perm) { + throw new UnsupportedOperationException(); + } + + @Override + public void kick(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void ban(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public double getHealth() { + throw new UnsupportedOperationException(); + } + + @Override + public void setHealth(double health) { + throw new UnsupportedOperationException(); + } + + @Override + public double getMaxHealth() { + throw new UnsupportedOperationException(); + } + + @Override + public double getFoodLevel() { + throw new UnsupportedOperationException(); + } + + @Override + public void setFoodLevel(double foodLevel) { + throw new UnsupportedOperationException(); + } + + @Override + public double getSaturation() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSaturation(double saturation) { + throw new UnsupportedOperationException(); + } + + @Override + public float getExhaustion() { + throw new UnsupportedOperationException(); + } + + @Override + public void setExhaustion(float exhaustion) { + throw new UnsupportedOperationException(); + } + + @Override + public WeatherType getPlayerWeather() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPlayerWeather(WeatherType weather) { + throw new UnsupportedOperationException(); + } + + @Override + public void resetPlayerWeather() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPlayerTimeRelative() { + throw new UnsupportedOperationException(); + } + + @Override + public long getPlayerTimeOffset() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPlayerTime(long time, boolean relative) { + throw new UnsupportedOperationException(); + } + + @Override + public void resetPlayerTime() { + throw new UnsupportedOperationException(); + } + + @Override + public void printRaw(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void printDebug(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void print(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public void printError(String msg) { + throw new UnsupportedOperationException(); + } + + @Override + public World getWorld() { + throw new UnsupportedOperationException(); + } + + @Override + public BaseItemStack getItemInHand(HandSide handSide) { + throw new UnsupportedOperationException(); + } + + @Override + public void giveItem(BaseItemStack itemStack) { + throw new UnsupportedOperationException(); + } + + @Override + public BlockBag getInventoryBlockBag() { + throw new UnsupportedOperationException(); + } + + @Override + public void setPosition(Vector3 pos, float pitch, float yaw) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public BaseEntity getState() { + throw new UnsupportedOperationException(); + } + + @Override + public Location getLocation() { + throw new UnsupportedOperationException(); + } + + @Override + public void setCompassTarget(Location location) { + throw new UnsupportedOperationException(); + } + + @Override + public SessionKey getSessionKey() { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T getFacet(Class cls) { + throw new UnsupportedOperationException(); + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java new file mode 100644 index 000000000..5ed933c5c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitPlayer.java @@ -0,0 +1,221 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldedit.world.weather.WeatherTypes; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.util.MessagingUtil; +import io.papermc.lib.PaperLib; +import org.bukkit.BanList.Type; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class BukkitPlayer extends com.sk89q.worldedit.bukkit.BukkitPlayer implements LocalPlayer { + + protected final WorldGuardPlugin plugin; + private final boolean silenced; + private String name; + + public BukkitPlayer(WorldGuardPlugin plugin, Player player) { + this(plugin, player, false); + } + + BukkitPlayer(WorldGuardPlugin plugin, Player player, boolean silenced) { + super(player); + this.plugin = plugin; + this.silenced = silenced; + } + + @Override + public String getName() { + if (this.name == null) { + // getName() takes longer than before in newer versions of Minecraft + this.name = getPlayer().getName(); + } + return name; + } + + @Override + public boolean hasGroup(String group) { + return plugin.inGroup(getPlayer(), group); + } + + @Override + public void kick(String msg) { + if (!silenced) { + getPlayer().kickPlayer(msg); + } + } + + @Override + public void ban(String msg) { + if (!silenced) { + Bukkit.getBanList(Type.NAME).addBan(getName(), null, null, null); + getPlayer().kickPlayer(msg); + } + } + + @Override + public double getHealth() { + return getPlayer().getHealth(); + } + + @Override + public void setHealth(double health) { + getPlayer().setHealth(health); + } + + @Override + public double getMaxHealth() { + return getPlayer().getMaxHealth(); + } + + @Override + public double getFoodLevel() { + return getPlayer().getFoodLevel(); + } + + @Override + public void setFoodLevel(double foodLevel) { + getPlayer().setFoodLevel((int) foodLevel); + } + + @Override + public double getSaturation() { + return getPlayer().getSaturation(); + } + + @Override + public void setSaturation(double saturation) { + getPlayer().setSaturation((float) saturation); + } + + @Override + public float getExhaustion() { + return getPlayer().getExhaustion(); + } + + @Override + public void setExhaustion(float exhaustion) { + getPlayer().setExhaustion(exhaustion); + } + + @Override + public WeatherType getPlayerWeather() { + org.bukkit.WeatherType playerWeather = getPlayer().getPlayerWeather(); + return playerWeather == null ? null : playerWeather == org.bukkit.WeatherType.CLEAR ? WeatherTypes.CLEAR : WeatherTypes.RAIN; + } + + @Override + public void setPlayerWeather(WeatherType weather) { + getPlayer().setPlayerWeather(weather == WeatherTypes.CLEAR ? org.bukkit.WeatherType.CLEAR : org.bukkit.WeatherType.DOWNFALL); + } + + @Override + public void resetPlayerWeather() { + getPlayer().resetPlayerWeather(); + } + + @Override + public boolean isPlayerTimeRelative() { + return getPlayer().isPlayerTimeRelative(); + } + + @Override + public long getPlayerTimeOffset() { + return getPlayer().getPlayerTimeOffset(); + } + + @Override + public void setPlayerTime(long time, boolean relative) { + getPlayer().setPlayerTime(time, relative); + } + + @Override + public void resetPlayerTime() { + getPlayer().resetPlayerTime(); + } + + @Override + public int getFireTicks() { + return getPlayer().getFireTicks(); + } + + @Override + public void setFireTicks(int fireTicks) { + getPlayer().setFireTicks(fireTicks); + } + + @Override + public void setCompassTarget(Location location) { + getPlayer().setCompassTarget(BukkitAdapter.adapt(location)); + } + + @Override + public void sendTitle(String title, String subtitle) { + if (WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(getWorld()).forceDefaultTitleTimes) { + getPlayer().sendTitle(title, subtitle, 10, 70, 20); + } else { + getPlayer().sendTitle(title, subtitle, -1, -1, -1); + } + } + + @Override + public void resetFallDistance() { + getPlayer().setFallDistance(0); + } + + @Override + public void teleport(Location location, String successMessage, String failMessage) { + PaperLib.teleportAsync(getPlayer(), BukkitAdapter.adapt(location)) + .thenApply(success -> { + if (success) { + // The success message can be cleared via flag + if (!successMessage.isEmpty()) { + MessagingUtil.sendStringToChat(this, successMessage); + } + } else { + printError(failMessage); + } + return success; + }); + } + + @Override + public String[] getGroups() { + return plugin.getGroups(getPlayer()); + } + + @Override + public void printRaw(String msg) { + if (!silenced) { + super.printRaw(msg); + } + } + + @Override + public boolean hasPermission(String perm) { + return plugin.hasPermission(getPlayer(), perm); + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitRegionContainer.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitRegionContainer.java new file mode 100644 index 000000000..79d7b3446 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitRegionContainer.java @@ -0,0 +1,132 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.ChunkUnloadEvent; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +public class BukkitRegionContainer extends RegionContainer { + + /** + * Invalidation frequency in ticks. + */ + private static final int CACHE_INVALIDATION_INTERVAL = 2; + + private final WorldGuardPlugin plugin; + + /** + * Create a new instance. + * + * @param plugin the plugin + */ + public BukkitRegionContainer(WorldGuardPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void initialize() { + super.initialize(); + Bukkit.getPluginManager().registerEvents(new Listener() { + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + load(BukkitAdapter.adapt(event.getWorld())); + } + + @EventHandler + public void onWorldUnload(WorldUnloadEvent event) { + unload(BukkitAdapter.adapt(event.getWorld())); + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + RegionManager manager = get(BukkitAdapter.adapt(event.getWorld())); + if (manager != null) { + Chunk chunk = event.getChunk(); + manager.loadChunk(BlockVector2.at(chunk.getX(), chunk.getZ())); + } + } + + @EventHandler + public void onChunkUnload(ChunkUnloadEvent event) { + RegionManager manager = get(BukkitAdapter.adapt(event.getWorld())); + if (manager != null) { + Chunk chunk = event.getChunk(); + manager.unloadChunk(BlockVector2.at(chunk.getX(), chunk.getZ())); + } + } + }, plugin); + + Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, cache::invalidateAll, CACHE_INVALIDATION_INTERVAL, CACHE_INVALIDATION_INTERVAL); + } + + public void shutdown() { + container.shutdown(); + } + + @Override + @Nullable + protected RegionManager load(World world) { + checkNotNull(world); + + WorldConfiguration config = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + if (!config.useRegions) { + return null; + } + + RegionManager manager; + + synchronized (lock) { + manager = container.load(world.getName()); + + if (manager != null) { + // Bias the region data for loaded chunks + List positions = new ArrayList<>(); + for (Chunk chunk : ((BukkitWorld) world).getWorld().getLoadedChunks()) { + positions.add(BlockVector2.at(chunk.getX(), chunk.getZ())); + } + manager.loadChunks(positions); + } + } + + return manager; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitStringMatcher.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitStringMatcher.java new file mode 100644 index 000000000..86445fdba --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitStringMatcher.java @@ -0,0 +1,246 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.internal.platform.StringMatcher; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class BukkitStringMatcher implements StringMatcher { + + @Override + public World matchWorld(Actor sender, String filter) throws CommandException { + List worlds = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds(); + + // Handle special hash tag groups + if (filter.charAt(0) == '#') { + // #main for the main world + if (filter.equalsIgnoreCase("#main")) { + return worlds.get(0); + + // #normal for the first normal world + } else if (filter.equalsIgnoreCase("#normal")) { + for (World world : worlds) { + if (BukkitAdapter.adapt(world).getEnvironment() == org.bukkit.World.Environment.NORMAL) { + return world; + } + } + + throw new CommandException("No normal world found."); + + // #nether for the first nether world + } else if (filter.equalsIgnoreCase("#nether")) { + for (World world : worlds) { + if (BukkitAdapter.adapt(world).getEnvironment() == org.bukkit.World.Environment.NETHER) { + return world; + } + } + + throw new CommandException("No nether world found."); + + // #end for the first nether world + } else if (filter.equalsIgnoreCase("#end")) { + for (World world : worlds) { + if (BukkitAdapter.adapt(world).getEnvironment() == org.bukkit.World.Environment.THE_END) { + return world; + } + } + + throw new CommandException("No end world found."); + + // Handle getting a world from a player + } else if (filter.matches("^#player$")) { + String[] parts = filter.split(":", 2); + + // They didn't specify an argument for the player! + if (parts.length == 1) { + throw new CommandException("Argument expected for #player."); + } + + return matchPlayers(sender, parts[1]).iterator().next().getWorld(); + } else { + throw new CommandException("Invalid identifier '" + filter + "'."); + } + } + + for (World world : worlds) { + if (world.getName().equals(filter)) { + return world; + } + } + + throw new CommandException("No world by that exact name found."); + } + + @Override + public List matchPlayerNames(String filter) { + List wgPlayers = Bukkit.getServer().getOnlinePlayers().stream().map(player -> WorldGuardPlugin.inst().wrapPlayer(player)).collect(Collectors.toList()); + + filter = filter.toLowerCase(); + + // Allow exact name matching + if (filter.charAt(0) == '@' && filter.length() >= 2) { + filter = filter.substring(1); + + for (LocalPlayer player : wgPlayers) { + if (player.getName().equalsIgnoreCase(filter)) { + List list = new ArrayList<>(); + list.add(player); + return list; + } + } + + return new ArrayList<>(); + // Allow partial name matching + } else if (filter.charAt(0) == '*' && filter.length() >= 2) { + filter = filter.substring(1); + + List list = new ArrayList<>(); + + for (LocalPlayer player : wgPlayers) { + if (player.getName().toLowerCase().contains(filter)) { + list.add(player); + } + } + + return list; + + // Start with name matching + } else { + List list = new ArrayList<>(); + + for (LocalPlayer player : wgPlayers) { + if (player.getName().toLowerCase().startsWith(filter)) { + list.add(player); + } + } + + return list; + } + } + + @Override + public Iterable matchPlayers(Actor source, String filter) throws CommandException { + if (Bukkit.getServer().getOnlinePlayers().isEmpty()) { + throw new CommandException("No players matched query."); + } + + List wgPlayers = Bukkit.getServer().getOnlinePlayers().stream().map(player -> WorldGuardPlugin.inst().wrapPlayer(player)).collect(Collectors.toList()); + + if (filter.equals("*")) { + return checkPlayerMatch(wgPlayers); + } + + // Handle special hash tag groups + if (filter.charAt(0) == '#') { + // Handle #world, which matches player of the same world as the + // calling source + if (filter.equalsIgnoreCase("#world")) { + List players = new ArrayList<>(); + LocalPlayer sourcePlayer = WorldGuard.getInstance().checkPlayer(source); + World sourceWorld = sourcePlayer.getWorld(); + + for (LocalPlayer player : wgPlayers) { + if (player.getWorld().equals(sourceWorld)) { + players.add(player); + } + } + + return checkPlayerMatch(players); + + // Handle #near, which is for nearby players. + } else if (filter.equalsIgnoreCase("#near")) { + List players = new ArrayList<>(); + LocalPlayer sourcePlayer = WorldGuard.getInstance().checkPlayer(source); + World sourceWorld = sourcePlayer.getWorld(); + Vector3 sourceVector = sourcePlayer.getLocation().toVector(); + + for (LocalPlayer player : wgPlayers) { + if (player.getWorld().equals(sourceWorld) && player.getLocation().toVector().distanceSq(sourceVector) < 900) { + players.add(player); + } + } + + return checkPlayerMatch(players); + + } else { + throw new CommandException("Invalid group '" + filter + "'."); + } + } + + List players = matchPlayerNames(filter); + + return checkPlayerMatch(players); + } + + @Override + public Actor matchPlayerOrConsole(Actor sender, String filter) throws CommandException { + // Let's see if console is wanted + if (filter.equalsIgnoreCase("#console") + || filter.equalsIgnoreCase("*console*") + || filter.equalsIgnoreCase("!")) { + return WorldGuardPlugin.inst().wrapCommandSender(Bukkit.getServer().getConsoleSender()); + } + + return matchSinglePlayer(sender, filter); + } + + @Override + public World getWorldByName(String worldName) { + final org.bukkit.World bukkitW = Bukkit.getServer().getWorld(worldName); + if (bukkitW == null) { + return null; + } + return BukkitAdapter.adapt(bukkitW); + } + + @Override + public String replaceMacros(Actor sender, String message) { + Collection online = Bukkit.getServer().getOnlinePlayers(); + + message = message.replace("%name%", sender.getName()); + message = message.replace("%id%", sender.getUniqueId().toString()); + message = message.replace("%online%", String.valueOf(online.size())); + + if (sender instanceof LocalPlayer) { + LocalPlayer player = (LocalPlayer) sender; + World world = (World) player.getExtent(); + + message = message.replace("%world%", world.getName()); + message = message.replace("%health%", String.valueOf(player.getHealth())); + } + + return message; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitUtil.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitUtil.java new file mode 100644 index 000000000..c290b7cfe --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitUtil.java @@ -0,0 +1,113 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.blacklist.target.BlockTarget; +import com.sk89q.worldguard.blacklist.target.ItemTarget; +import com.sk89q.worldguard.blacklist.target.Target; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +public class BukkitUtil { + + private BukkitUtil() { + } + + /** + * Checks if the given potion is a vial of water. + * + * @param item the item to check + * @return true if it's a water vial + */ + public static boolean isWaterPotion(ItemStack item) { + return (item.getDurability() & 0x3F) == 0; + } + + /** + * Get just the potion effect bits. This is to work around bugs with potion + * parsing. + * + * @param item item + * @return new bits + */ + public static int getPotionEffectBits(ItemStack item) { + return item.getDurability() & 0x3F; + } + + /** + * Get a blacklist target for the given block. + * + * @param block the block + * @param effectiveMaterial The effective material, if different + * @return a target + */ + public static Target createTarget(Block block, Material effectiveMaterial) { + checkNotNull(block); + checkNotNull(block.getType()); + if (block.getType() == effectiveMaterial) { + return createTarget(block.getType()); + } else { + return createTarget(effectiveMaterial); + } + } + + /** + * Get a blacklist target for the given block. + * + * @param block the block + * @return a target + */ + public static Target createTarget(Block block) { + checkNotNull(block); + checkNotNull(block.getType()); + return createTarget(block.getType()); + } + + /** + * Get a blacklist target for the given item. + * + * @param item the item + * @return a target + */ + public static Target createTarget(ItemStack item) { + checkNotNull(item); + checkNotNull(item.getType()); + return createTarget(item.getType()); // Delegate it, ItemStacks can contain both Blocks and Items in Spigot + } + + /** + * Get a blacklist target for the given material. + * + * @param material the material + * @return a target + */ + public static Target createTarget(Material material) { + checkNotNull(material); + if (material.isBlock()) { + return new BlockTarget(BukkitAdapter.asBlockType(material)); + } else { + return new ItemTarget(BukkitAdapter.asItemType(material)); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldConfiguration.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldConfiguration.java new file mode 100644 index 000000000..708618ef0 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldConfiguration.java @@ -0,0 +1,448 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.util.yaml.YAMLFormat; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.report.Unreported; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.entity.EntityTypes; +import com.sk89q.worldedit.world.item.ItemTypes; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.blacklist.BlacklistLoggerHandler; +import com.sk89q.worldguard.blacklist.logger.ConsoleHandler; +import com.sk89q.worldguard.blacklist.logger.DatabaseHandler; +import com.sk89q.worldguard.blacklist.logger.FileHandler; +import com.sk89q.worldguard.blacklist.target.TargetMatcherParseException; +import com.sk89q.worldguard.blacklist.target.TargetMatcherParser; +import com.sk89q.worldguard.bukkit.chest.BukkitSignChestProtection; +import com.sk89q.worldguard.bukkit.internal.TargetMatcherSet; +import com.sk89q.worldguard.chest.ChestProtection; +import com.sk89q.worldguard.commands.CommandUtils; +import com.sk89q.worldguard.config.YamlWorldConfiguration; +import org.bukkit.potion.PotionEffectType; +import org.yaml.snakeyaml.error.YAMLException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; + +/** + * Holds the configuration for individual worlds. + * + * @author sk89q + * @author Michael + */ +public class BukkitWorldConfiguration extends YamlWorldConfiguration { + + private static final TargetMatcherParser matcherParser = new TargetMatcherParser(); + + @Unreported private String worldName; + + @Unreported private ChestProtection chestProtection = new BukkitSignChestProtection(); + + /* Configuration data start */ + public Set blockPotions; + public TargetMatcherSet allowAllInteract; + public TargetMatcherSet blockUseAtFeet; + public boolean usePaperEntityOrigin; + /* Configuration data end */ + + /** + * Construct the object. + * + * @param plugin The WorldGuardPlugin instance + * @param worldName The world name that this BukkitWorldConfiguration is for. + * @param parentConfig The parent configuration to read defaults from + */ + public BukkitWorldConfiguration(WorldGuardPlugin plugin, String worldName, YAMLProcessor parentConfig) { + File baseFolder = new File(plugin.getDataFolder(), "worlds/" + worldName); + File configFile = new File(baseFolder, "config.yml"); + blacklistFile = new File(baseFolder, "blacklist.txt"); + + this.worldName = worldName; + this.parentConfig = parentConfig; + + plugin.createDefaultConfiguration(configFile, "config_world.yml"); + plugin.createDefaultConfiguration(blacklistFile, "blacklist.txt"); + + config = new YAMLProcessor(configFile, true, YAMLFormat.EXTENDED); + loadConfiguration(); + + if (summaryOnStart) { + log.info("Loaded configuration for world '" + worldName + "'"); + } + } + + private TargetMatcherSet getTargetMatchers(String node) { + TargetMatcherSet set = new TargetMatcherSet(); + List inputs = parentConfig.getStringList(node, null); + + if (inputs == null || inputs.isEmpty()) { + parentConfig.setProperty(node, new ArrayList()); + } + + if (config.getProperty(node) != null) { + inputs = config.getStringList(node, null); + } + + if (inputs == null || inputs.isEmpty()) { + return set; + } + + for (String input : inputs) { + try { + set.add(matcherParser.fromInput(input)); + } catch (TargetMatcherParseException e) { + log.warning("Failed to parse the block / item type specified as '" + input + "'"); + } + } + + return set; + } + + /** + * Load the configuration. + */ + @Override + public void loadConfiguration() { + try { + config.load(); + } catch (IOException e) { + log.log(Level.SEVERE, "Error reading configuration for world " + worldName + ": ", e); + } catch (YAMLException e) { + log.severe("Error parsing configuration for world " + worldName + ". "); + throw e; + } + + summaryOnStart = getBoolean("summary-on-start", true); + opPermissions = getBoolean("op-permissions", true); + + buildPermissions = getBoolean("build-permission-nodes.enable", false); + buildPermissionDenyMessage = CommandUtils.replaceColorMacros( + getString("build-permission-nodes.deny-message", "&eSorry, but you are not permitted to do that here.")); + + strictEntitySpawn = getBoolean("event-handling.block-entity-spawns-with-untraceable-cause", false); + allowAllInteract = getTargetMatchers("event-handling.interaction-whitelist"); + blockUseAtFeet = getTargetMatchers("event-handling.emit-block-use-at-feet"); + ignoreHopperMoveEvents = getBoolean("event-handling.ignore-hopper-item-move-events", false); + breakDeniedHoppers = getBoolean("event-handling.break-hoppers-on-denied-move", true); + + usePaperEntityOrigin = getBoolean("regions.use-paper-entity-origin", false); + + itemDurability = getBoolean("protection.item-durability", true); + removeInfiniteStacks = getBoolean("protection.remove-infinite-stacks", false); + disableExpDrops = getBoolean("protection.disable-xp-orb-drops", false); + disableObsidianGenerators = getBoolean("protection.disable-obsidian-generators", false); + + useMaxPriorityAssociation = getBoolean("protection.use-max-priority-association", false); + + blockPotions = new HashSet<>(); + for (String potionName : getStringList("gameplay.block-potions", null)) { + PotionEffectType effect = PotionEffectType.getByName(potionName); + + if (effect == null) { + log.warning("Unknown potion effect type '" + potionName + "'"); + } else { + blockPotions.add(effect); + } + } + blockPotionsAlways = getBoolean("gameplay.block-potions-overly-reliably", false); + disableConduitEffects = getBoolean("gameplay.disable-conduit-effects", false); + + simulateSponge = getBoolean("simulation.sponge.enable", false); + spongeRadius = Math.max(1, getInt("simulation.sponge.radius", 3)) - 1; + redstoneSponges = getBoolean("simulation.sponge.redstone", false); + + pumpkinScuba = getBoolean("default.pumpkin-scuba", false); + disableHealthRegain = getBoolean("default.disable-health-regain", false); + + noPhysicsGravel = getBoolean("physics.no-physics-gravel", false); + noPhysicsSand = getBoolean("physics.no-physics-sand", false); + ropeLadders = getBoolean("physics.vine-like-rope-ladders", false); + allowPortalAnywhere = getBoolean("physics.allow-portal-anywhere", false); + preventWaterDamage = new HashSet<>(convertLegacyBlocks(getStringList("physics.disable-water-damage-blocks", null))); + + blockTNTExplosions = getBoolean("ignition.block-tnt", false); + blockTNTBlockDamage = getBoolean("ignition.block-tnt-block-damage", false); + blockLighter = getBoolean("ignition.block-lighter", false); + + preventLavaFire = getBoolean("fire.disable-lava-fire-spread", false); + disableFireSpread = getBoolean("fire.disable-all-fire-spread", false); + disableFireSpreadBlocks = new HashSet<>(convertLegacyBlocks(getStringList("fire.disable-fire-spread-blocks", null))); + allowedLavaSpreadOver = new HashSet<>(convertLegacyBlocks(getStringList("fire.lava-spread-blocks", null))); + + blockCreeperExplosions = getBoolean("mobs.block-creeper-explosions", false); + blockCreeperBlockDamage = getBoolean("mobs.block-creeper-block-damage", false); + blockWitherExplosions = getBoolean("mobs.block-wither-explosions", false); + blockWitherBlockDamage = getBoolean("mobs.block-wither-block-damage", false); + blockWitherSkullExplosions = getBoolean("mobs.block-wither-skull-explosions", false); + blockWitherSkullBlockDamage = getBoolean("mobs.block-wither-skull-block-damage", false); + blockEnderDragonBlockDamage = getBoolean("mobs.block-enderdragon-block-damage", false); + blockEnderDragonPortalCreation = getBoolean("mobs.block-enderdragon-portal-creation", false); + blockFireballExplosions = getBoolean("mobs.block-fireball-explosions", false); + blockFireballBlockDamage = getBoolean("mobs.block-fireball-block-damage", false); + antiWolfDumbness = getBoolean("mobs.anti-wolf-dumbness", false); + allowTamedSpawns = getBoolean("mobs.allow-tamed-spawns", true); + disableEndermanGriefing = getBoolean("mobs.disable-enderman-griefing", false); + disableSnowmanTrails = getBoolean("mobs.disable-snowman-trails", false); + blockEntityPaintingDestroy = getBoolean("mobs.block-painting-destroy", false); + blockEntityItemFrameDestroy = getBoolean("mobs.block-item-frame-destroy", false); + blockEntityArmorStandDestroy = getBoolean("mobs.block-armor-stand-destroy", false); + blockPluginSpawning = getBoolean("mobs.block-plugin-spawning", true); + blockGroundSlimes = getBoolean("mobs.block-above-ground-slimes", false); + blockOtherExplosions = getBoolean("mobs.block-other-explosions", false); + blockZombieDoorDestruction = getBoolean("mobs.block-zombie-door-destruction", false); + blockEntityVehicleEntry = getBoolean("mobs.block-vehicle-entry", false); + + disableFallDamage = getBoolean("player-damage.disable-fall-damage", false); + disableLavaDamage = getBoolean("player-damage.disable-lava-damage", false); + disableFireDamage = getBoolean("player-damage.disable-fire-damage", false); + disableLightningDamage = getBoolean("player-damage.disable-lightning-damage", false); + disableDrowningDamage = getBoolean("player-damage.disable-drowning-damage", false); + disableSuffocationDamage = getBoolean("player-damage.disable-suffocation-damage", false); + disableContactDamage = getBoolean("player-damage.disable-contact-damage", false); + teleportOnSuffocation = getBoolean("player-damage.teleport-on-suffocation", false); + disableVoidDamage = getBoolean("player-damage.disable-void-damage", false); + teleportOnVoid = getBoolean("player-damage.teleport-on-void-falling", false); + safeFallOnVoid = getBoolean("player-damage.reset-fall-on-void-teleport", false); + disableExplosionDamage = getBoolean("player-damage.disable-explosion-damage", false); + disableMobDamage = getBoolean("player-damage.disable-mob-damage", false); + disableDeathMessages = getBoolean("player-damage.disable-death-messages", false); + + signChestProtection = getBoolean("chest-protection.enable", false); + disableSignChestProtectionCheck = getBoolean("chest-protection.disable-off-check", false); + + disableCreatureCropTrampling = getBoolean("crops.disable-creature-trampling", false); + disablePlayerCropTrampling = getBoolean("crops.disable-player-trampling", false); + + disableCreatureTurtleEggTrampling = getBoolean("turtle-egg.disable-creature-trampling", false); + disablePlayerTurtleEggTrampling = getBoolean("turtle-egg.disable-player-trampling", false); + + disallowedLightningBlocks = new HashSet<>(convertLegacyBlocks(getStringList("weather.prevent-lightning-strike-blocks", null))); + preventLightningFire = getBoolean("weather.disable-lightning-strike-fire", false); + disableThunder = getBoolean("weather.disable-thunderstorm", false); + disableWeather = getBoolean("weather.disable-weather", false); + disablePigZap = getBoolean("weather.disable-pig-zombification", false); + disableVillagerZap = getBoolean("weather.disable-villager-witchification", false); + disableCreeperPower = getBoolean("weather.disable-powered-creepers", false); + alwaysRaining = getBoolean("weather.always-raining", false); + alwaysThundering = getBoolean("weather.always-thundering", false); + + disableMushroomSpread = getBoolean("dynamics.disable-mushroom-spread", false); + disableIceMelting = getBoolean("dynamics.disable-ice-melting", false); + disableSnowMelting = getBoolean("dynamics.disable-snow-melting", false); + disableSnowFormation = getBoolean("dynamics.disable-snow-formation", false); + disableIceFormation = getBoolean("dynamics.disable-ice-formation", false); + disableLeafDecay = getBoolean("dynamics.disable-leaf-decay", false); + disableGrassGrowth = getBoolean("dynamics.disable-grass-growth", false); + disableMyceliumSpread = getBoolean("dynamics.disable-mycelium-spread", false); + disableVineGrowth = getBoolean("dynamics.disable-vine-growth", false); + disableCropGrowth = getBoolean("dynamics.disable-crop-growth", false); + disableSoilDehydration = getBoolean("dynamics.disable-soil-dehydration", false); + disableCoralBlockFade = getBoolean("dynamics.disable-coral-block-fade", false); + allowedSnowFallOver = new HashSet<>(convertLegacyBlocks(getStringList("dynamics.snow-fall-blocks", null))); + + useRegions = getBoolean("regions.enable", true); + regionInvinciblityRemovesMobs = getBoolean("regions.invincibility-removes-mobs", false); + regionCancelEmptyChatEvents = getBoolean("regions.cancel-chat-without-recipients", true); + regionNetherPortalProtection = getBoolean("regions.nether-portal-protection", true); + forceDefaultTitleTimes = config.getBoolean("regions.titles-always-use-default-times", true); // note: technically not region-specific, but we only use it for the title flags + fakePlayerBuildOverride = getBoolean("regions.fake-player-build-override", true); + explosionFlagCancellation = getBoolean("regions.explosion-flags-block-entity-damage", true); + highFreqFlags = getBoolean("regions.high-frequency-flags", false); + checkLiquidFlow = getBoolean("regions.protect-against-liquid-flow", false); + regionWand = convertLegacyItem(getString("regions.wand", ItemTypes.LEATHER.getId())); + maxClaimVolume = getInt("regions.max-claim-volume", 30000); + claimOnlyInsideExistingRegions = getBoolean("regions.claim-only-inside-existing-regions", false); + boundedLocationFlags = getBoolean("regions.location-flags-only-inside-regions", false); + + maxRegionCountPerPlayer = getInt("regions.max-region-count-per-player.default", 7); + maxRegionCounts = new HashMap<>(); + maxRegionCounts.put(null, maxRegionCountPerPlayer); + + for (String key : getKeys("regions.max-region-count-per-player")) { + if (!key.equalsIgnoreCase("default")) { + Object val = getProperty("regions.max-region-count-per-player." + key); + if (val instanceof Number) { + maxRegionCounts.put(key, ((Number) val).intValue()); + } + } + } + + // useiConomy = getBoolean("iconomy.enable", false); + // buyOnClaim = getBoolean("iconomy.buy-on-claim", false); + // buyOnClaimPrice = getDouble("iconomy.buy-on-claim-price", 1.0); + + blockCreatureSpawn = new HashSet<>(); + for (String creatureName : getStringList("mobs.block-creature-spawn", null)) { + EntityType creature = EntityTypes.get(creatureName.toLowerCase()); + + if (creature == null) { + log.warning("Unknown entity type '" + creatureName + "'"); + } else { + blockCreatureSpawn.add(creature); + } + } + + boolean useBlacklistAsWhitelist = getBoolean("blacklist.use-as-whitelist", false); + + // Console log configuration + boolean logConsole = getBoolean("blacklist.logging.console.enable", true); + + // Database log configuration + boolean logDatabase = getBoolean("blacklist.logging.database.enable", false); + String dsn = getString("blacklist.logging.database.dsn", "jdbc:mysql://localhost:3306/minecraft"); + String user = getString("blacklist.logging.database.user", "root"); + String pass = getString("blacklist.logging.database.pass", ""); + String table = getString("blacklist.logging.database.table", "blacklist_events"); + + // File log configuration + boolean logFile = getBoolean("blacklist.logging.file.enable", false); + String logFilePattern = getString("blacklist.logging.file.path", "worldguard/logs/%Y-%m-%d.log"); + int logFileCacheSize = Math.max(1, getInt("blacklist.logging.file.open-files", 10)); + + // Load the blacklist + try { + // If there was an existing blacklist, close loggers + if (blacklist != null) { + blacklist.getLogger().close(); + } + + // First load the blacklist data from worldguard-blacklist.txt + Blacklist blist = new Blacklist(useBlacklistAsWhitelist); + blist.load(blacklistFile); + + // If the blacklist is empty, then set the field to null + // and save some resources + if (blist.isEmpty()) { + this.blacklist = null; + } else { + this.blacklist = blist; + if (summaryOnStart) { + log.log(Level.INFO, "({0}) Blacklist loaded with {1} entries.", + new Object[]{worldName, blacklist.getItemCount()}); + } + + BlacklistLoggerHandler blacklistLogger = blist.getLogger(); + + if (logDatabase) { + blacklistLogger.addHandler(new DatabaseHandler(dsn, user, pass, table, worldName, log)); + } + + if (logConsole) { + blacklistLogger.addHandler(new ConsoleHandler(worldName, log)); + } + + if (logFile) { + FileHandler handler = + new FileHandler(logFilePattern, logFileCacheSize, worldName, log); + blacklistLogger.addHandler(handler); + } + } + } catch (FileNotFoundException e) { + log.log(Level.WARNING, "WorldGuard blacklist does not exist."); + } catch (IOException e) { + log.log(Level.WARNING, "Could not load WorldGuard blacklist: " + + e.getMessage()); + } + + // Print an overview of settings + if (summaryOnStart) { + log.log(Level.INFO, blockTNTExplosions + ? "(" + worldName + ") TNT ignition is blocked." + : "(" + worldName + ") TNT ignition is PERMITTED."); + log.log(Level.INFO, blockLighter + ? "(" + worldName + ") Lighters are blocked." + : "(" + worldName + ") Lighters are PERMITTED."); + log.log(Level.INFO, preventLavaFire + ? "(" + worldName + ") Lava fire is blocked." + : "(" + worldName + ") Lava fire is PERMITTED."); + + if (disableFireSpread) { + log.log(Level.INFO, "(" + worldName + ") All fire spread is disabled."); + } else { + if (!disableFireSpreadBlocks.isEmpty()) { + log.log(Level.INFO, "(" + worldName + + ") Fire spread is limited to " + + disableFireSpreadBlocks.size() + " block types."); + } else { + log.log(Level.INFO, "(" + worldName + + ") Fire spread is UNRESTRICTED."); + } + } + } + + config.setHeader(CONFIG_HEADER); + + config.save(); + } + + public boolean isChestProtected(Location block, LocalPlayer player) { + if (!signChestProtection) { + return false; + } + if (player.hasPermission("worldguard.chest-protection.override") + || player.hasPermission("worldguard.override.chest-protection")) { + return false; + } + return chestProtection.isProtected(block, player); + } + + public boolean isChestProtected(Location block) { + + return signChestProtection && chestProtection.isProtected(block, null); + } + + public boolean isChestProtectedPlacement(Location block, LocalPlayer player) { + if (!signChestProtection) { + return false; + } + if (player.hasPermission("worldguard.chest-protection.override") + || player.hasPermission("worldguard.override.chest-protection")) { + return false; + } + return chestProtection.isProtectedPlacement(block, player); + } + + public boolean isAdjacentChestProtected(Location block, LocalPlayer player) { + if (!signChestProtection) { + return false; + } + if (player.hasPermission("worldguard.chest-protection.override") + || player.hasPermission("worldguard.override.chest-protection")) { + return false; + } + return chestProtection.isAdjacentChestProtected(block, player); + } + + public ChestProtection getChestProtection() { + return chestProtection; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java new file mode 100644 index 000000000..50e4ba0a6 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/BukkitWorldGuardPlatform.java @@ -0,0 +1,274 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.report.ReportList; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.gamemode.GameModes; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.profile.resolver.PaperPlayerService; +import com.sk89q.worldguard.bukkit.protection.events.flags.FlagContextCreateEvent; +import com.sk89q.worldguard.bukkit.session.BukkitSessionManager; +import com.sk89q.worldguard.bukkit.util.report.PerformanceReport; +import com.sk89q.worldguard.bukkit.util.report.PluginReport; +import com.sk89q.worldguard.bukkit.util.report.SchedulerReport; +import com.sk89q.worldguard.bukkit.util.report.ServerReport; +import com.sk89q.worldguard.bukkit.util.report.ServicesReport; +import com.sk89q.worldguard.bukkit.util.report.WorldReport; +import com.sk89q.worldguard.internal.platform.DebugHandler; +import com.sk89q.worldguard.internal.platform.StringMatcher; +import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; +import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.session.SessionManager; +import com.sk89q.worldguard.util.profile.cache.ProfileCache; +import com.sk89q.worldguard.util.profile.resolver.BukkitPlayerService; +import com.sk89q.worldguard.util.profile.resolver.CacheForwardingService; +import com.sk89q.worldguard.util.profile.resolver.CombinedProfileService; +import com.sk89q.worldguard.util.profile.resolver.HttpRepositoryService; +import com.sk89q.worldguard.util.profile.resolver.ProfileService; +import io.papermc.lib.PaperLib; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.permissions.Permissible; + +import javax.annotation.Nullable; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class BukkitWorldGuardPlatform implements WorldGuardPlatform { + + private BukkitSessionManager sessionManager; + private BukkitConfigurationManager configuration; + private BukkitRegionContainer regionContainer; + private BukkitDebugHandler debugHandler; + private StringMatcher stringMatcher; + + public BukkitWorldGuardPlatform() { + } + + @Override + public String getPlatformName() { + return "Bukkit-Official"; + } + + @Override + public String getPlatformVersion() { + return WorldGuardPlugin.inst().getDescription().getVersion(); + } + + @Override + public void notifyFlagContextCreate(FlagContext.FlagContextBuilder flagContextBuilder) { + Bukkit.getServer().getPluginManager().callEvent(new FlagContextCreateEvent(flagContextBuilder)); + } + + @Override + public BukkitConfigurationManager getGlobalStateManager() { + return configuration; + } + + @Override + public StringMatcher getMatcher() { + return stringMatcher; + } + + @Override + public SessionManager getSessionManager() { + return this.sessionManager; + } + + @Override + public void broadcastNotification(String message) { + Bukkit.broadcast(message, "worldguard.notify"); + Set subs = Bukkit.getPluginManager().getPermissionSubscriptions("worldguard.notify"); + for (Player player : Bukkit.getServer().getOnlinePlayers()) { + if (!(subs.contains(player) && player.hasPermission("worldguard.notify")) && + WorldGuardPlugin.inst().hasPermission(player, "worldguard.notify")) { // Make sure the player wasn't already broadcasted to. + player.sendMessage(message); + } + } + WorldGuard.logger.info(message); + } + + @Override + public void broadcastNotification(TextComponent component) { + List + wgPlayers = Bukkit.getServer().getOnlinePlayers().stream().map(player -> WorldGuardPlugin.inst().wrapPlayer(player)).collect( + Collectors.toList()); + + for (LocalPlayer player : wgPlayers) { + if (player.hasPermission("worldguard.notify")) { + player.print(component); + } + } + } + + @Override + public void load() { + stringMatcher = new BukkitStringMatcher(); + sessionManager = new BukkitSessionManager(); + configuration = new BukkitConfigurationManager(WorldGuardPlugin.inst()); + configuration.load(); + sessionManager.setUsingTimings(configuration.timedSessionHandlers); + regionContainer = new BukkitRegionContainer(WorldGuardPlugin.inst()); + regionContainer.initialize(); + debugHandler = new BukkitDebugHandler(WorldGuardPlugin.inst()); + } + + @Override + public void unload() { + configuration.unload(); + regionContainer.shutdown(); + } + + @Override + public RegionContainer getRegionContainer() { + return this.regionContainer; + } + + @Override + public DebugHandler getDebugHandler() { + return debugHandler; + } + + @Override + public GameMode getDefaultGameMode() { + return GameModes.get(Bukkit.getServer().getDefaultGameMode().name().toLowerCase()); + } + + @Override + public Path getConfigDir() { + return WorldGuardPlugin.inst().getDataFolder().toPath(); + } + + @Override + public void stackPlayerInventory(LocalPlayer localPlayer) { + boolean ignoreMax = localPlayer.hasPermission("worldguard.stack.illegitimate"); + + Player player = ((BukkitPlayer) localPlayer).getPlayer(); + + ItemStack[] items = player.getInventory().getContents(); + int len = items.length; + + int affected = 0; + + for (int i = 0; i < len; i++) { + ItemStack item = items[i]; + + // Avoid infinite stacks and stacks with durability + if (item == null || item.getAmount() <= 0 + || (!ignoreMax && item.getMaxStackSize() == 1)) { + continue; + } + + int max = ignoreMax ? 64 : item.getMaxStackSize(); + + if (item.getAmount() < max) { + int needed = max - item.getAmount(); // Number of needed items until max + + // Find another stack of the same type + for (int j = i + 1; j < len; j++) { + ItemStack item2 = items[j]; + + // Avoid infinite stacks and stacks with durability + if (item2 == null || item2.getAmount() <= 0 + || (!ignoreMax && item.getMaxStackSize() == 1)) { + continue; + } + + // Same type? + if (item2.isSimilar(item)) { + // This stack won't fit in the parent stack + if (item2.getAmount() > needed) { + item.setAmount(max); + item2.setAmount(item2.getAmount() - needed); + break; + // This stack will + } else { + items[j] = null; + item.setAmount(item.getAmount() + item2.getAmount()); + needed = max - item.getAmount(); + } + + affected++; + } + } + } + } + + if (affected > 0) { + player.getInventory().setContents(items); + } + } + + @Override + public void addPlatformReports(ReportList report) { + report.add(new ServerReport()); + report.add(new PluginReport()); + report.add(new SchedulerReport()); + report.add(new ServicesReport()); + report.add(new WorldReport()); + report.add(new PerformanceReport()); + } + + @Override + public ProfileService createProfileService(ProfileCache profileCache) { + List services = new ArrayList<>(); + if (PaperLib.isPaper()) { + // Paper has a shared cache + services.add(PaperPlayerService.getInstance()); + } else { + services.add(BukkitPlayerService.getInstance()); + } + services.add(HttpRepositoryService.forMinecraft()); + return new CacheForwardingService(new CombinedProfileService(services), + profileCache); + } + + @Nullable + @Override + public ProtectedRegion getSpawnProtection(World world) { + if (world instanceof BukkitWorld) { + org.bukkit.World bWorld = ((BukkitWorld) world).getWorld(); + if (bWorld.getUID().equals(Bukkit.getServer().getWorlds().get(0).getUID())) { + int radius = Bukkit.getServer().getSpawnRadius(); + if (radius > 0) { + BlockVector3 spawnLoc = BukkitAdapter.asBlockVector(bWorld.getSpawnLocation()); + return new ProtectedCuboidRegion("__spawn_protection__", + spawnLoc.subtract(radius, 0, radius).withY(world.getMinimumPoint().getY()), + spawnLoc.add(radius, 0, radius).withY(world.getMaxY())); + } + } + } + return null; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/ProtectionQuery.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/ProtectionQuery.java new file mode 100644 index 000000000..acad8c4f3 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/ProtectionQuery.java @@ -0,0 +1,153 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; + +import javax.annotation.Nullable; + +import static com.sk89q.worldguard.bukkit.event.DelegateEvents.setSilent; +import static com.sk89q.worldguard.bukkit.util.Events.fireAndTestCancel; + +/** + * A helper class to query whether a block or entity is protected by + * WorldGuard. + */ +public class ProtectionQuery { + + /** + * Test whether a block can be placed at a given location. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param location the location of the block + * @param newMaterial the new material + * @return true if the action is permitted + */ + public boolean testBlockPlace(@Nullable Object cause, Location location, Material newMaterial) { + return !fireAndTestCancel(setSilent(new PlaceBlockEvent(null, Cause.create(cause), location, newMaterial))); + } + + /** + * Test whether a block can be broken. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param block the block broken + * @return true if the action is permitted + */ + public boolean testBlockBreak(@Nullable Object cause, Block block) { + return !fireAndTestCancel(setSilent(new BreakBlockEvent(null, Cause.create(cause), block))); + } + + /** + * Test whether a block can be interacted with. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param block the block that is interacted with + * @return true if the action is permitted + */ + public boolean testBlockInteract(@Nullable Object cause, Block block) { + return !fireAndTestCancel(setSilent(new UseBlockEvent(null, Cause.create(cause), block))); + } + + /** + * Test whether an entity can be placed. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param location the location that the entity will be spawned at + * @param type the type that is to be spawned + * @return true if the action is permitted + */ + public boolean testEntityPlace(@Nullable Object cause, Location location, EntityType type) { + return !fireAndTestCancel(setSilent(new SpawnEntityEvent(null, Cause.create(cause), location, type))); + } + + /** + * Test whether an entity can be destroyed. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param entity the entity broken + * @return true if the action is permitted + */ + public boolean testEntityDestroy(@Nullable Object cause, Entity entity) { + return !fireAndTestCancel(setSilent(new SpawnEntityEvent(null, Cause.create(cause), entity))); + } + + /** + * Test whether an entity can be interacted with. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param entity the entity interacted with + * @return true if the action is permitted + */ + public boolean testEntityInteract(@Nullable Object cause, Entity entity) { + return !fireAndTestCancel(setSilent(new UseEntityEvent(null, Cause.create(cause), entity))); + } + + /** + * Test whether an entity can be damaged. + * + *

The cause does not have to be a player. It can be {@code null} + * if the cause is not known, although the checks will executed + * assuming that the actor is a non-member of all regions.

+ * + * @param cause the cause, which can be a block, an entity, or a player, or {@code null} if unknown + * @param entity the entity damaged + * @return true if the action is permitted + */ + public boolean testEntityDamage(@Nullable Object cause, Entity entity) { + return !fireAndTestCancel(setSilent(new DamageEntityEvent(null, Cause.create(cause), entity))); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java new file mode 100644 index 000000000..d1aef8858 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/WorldGuardPlugin.java @@ -0,0 +1,529 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit; + +import com.google.common.collect.ImmutableList; +import com.sk89q.bukkit.util.CommandsManagerRegistration; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.minecraft.util.commands.CommandUsageException; +import com.sk89q.minecraft.util.commands.CommandsManager; +import com.sk89q.minecraft.util.commands.MissingNestedCommandException; +import com.sk89q.minecraft.util.commands.SimpleInjector; +import com.sk89q.minecraft.util.commands.WrappedCommandException; +import com.sk89q.wepif.PermissionsResolverManager; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitCommandSender; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.bukkit.event.player.ProcessPlayerEvent; +import com.sk89q.worldguard.bukkit.listener.BlacklistListener; +import com.sk89q.worldguard.bukkit.listener.BlockedPotionsListener; +import com.sk89q.worldguard.bukkit.listener.BuildPermissionListener; +import com.sk89q.worldguard.bukkit.listener.ChestProtectionListener; +import com.sk89q.worldguard.bukkit.listener.DebuggingListener; +import com.sk89q.worldguard.bukkit.listener.EventAbstractionListener; +import com.sk89q.worldguard.bukkit.listener.InvincibilityListener; +import com.sk89q.worldguard.bukkit.listener.PlayerModesListener; +import com.sk89q.worldguard.bukkit.listener.PlayerMoveListener; +import com.sk89q.worldguard.bukkit.listener.RegionFlagsListener; +import com.sk89q.worldguard.bukkit.listener.RegionProtectionListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardBlockListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardCommandBookListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardEntityListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardHangingListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardPlayerListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardServerListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardVehicleListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardWeatherListener; +import com.sk89q.worldguard.bukkit.listener.WorldGuardWorldListener; +import com.sk89q.worldguard.bukkit.listener.WorldRulesListener; +import com.sk89q.worldguard.bukkit.session.BukkitSessionManager; +import com.sk89q.worldguard.bukkit.util.Events; +import com.sk89q.worldguard.bukkit.util.logging.ClassSourceValidator; +import com.sk89q.worldguard.commands.GeneralCommands; +import com.sk89q.worldguard.commands.ProtectionCommands; +import com.sk89q.worldguard.commands.ToggleCommands; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.registry.SimpleFlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.file.DirectoryYamlDriver; +import com.sk89q.worldguard.protection.managers.storage.sql.SQLDriver; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.logging.RecordMessagePrefixer; +import org.bstats.bukkit.Metrics; +import org.bstats.charts.DrilldownPie; +import org.bstats.charts.SimplePie; +import org.bstats.charts.SingleLineChart; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The main class for WorldGuard as a Bukkit plugin. + */ +public class WorldGuardPlugin extends JavaPlugin { + + private static WorldGuardPlugin inst; + private static BukkitWorldGuardPlatform platform; + private final CommandsManager commands; + private PlayerMoveListener playerMoveListener; + + private static final int BSTATS_PLUGIN_ID = 3283; + + /** + * Construct objects. Actual loading occurs when the plugin is enabled, so + * this merely instantiates the objects. + */ + public WorldGuardPlugin() { + inst = this; + commands = new CommandsManager() { + @Override + public boolean hasPermission(Actor player, String perm) { + return player.hasPermission(perm); + } + }; + } + + /** + * Get the current instance of WorldGuard + * @return WorldGuardPlugin instance + */ + public static WorldGuardPlugin inst() { + return inst; + } + + /** + * Called on plugin enable. + */ + @Override + public void onEnable() { + configureLogger(); + + getDataFolder().mkdirs(); // Need to create the plugins/WorldGuard folder + + PermissionsResolverManager.initialize(this); + + WorldGuard.getInstance().setPlatform(platform = new BukkitWorldGuardPlatform()); // Initialise WorldGuard + WorldGuard.getInstance().setup(); + BukkitSessionManager sessionManager = (BukkitSessionManager) platform.getSessionManager(); + + // Set the proper command injector + commands.setInjector(new SimpleInjector(WorldGuard.getInstance())); + + // Catch bad things being done by naughty plugins that include + // WorldGuard's classes + ClassSourceValidator verifier = new ClassSourceValidator(this); + verifier.reportMismatches(ImmutableList.of(ProtectedRegion.class, ProtectedCuboidRegion.class, Flag.class)); + + // Register command classes + final CommandsManagerRegistration reg = new CommandsManagerRegistration(this, commands); + reg.register(ToggleCommands.class); + reg.register(ProtectionCommands.class); + + getServer().getScheduler().scheduleSyncDelayedTask(this, () -> { + if (!platform.getGlobalStateManager().hasCommandBookGodMode()) { + reg.register(GeneralCommands.class); + } + }, 0L); + + getServer().getScheduler().scheduleSyncRepeatingTask(this, sessionManager, BukkitSessionManager.RUN_DELAY, BukkitSessionManager.RUN_DELAY); + + // Register events + getServer().getPluginManager().registerEvents(sessionManager, this); + (new WorldGuardPlayerListener(this)).registerEvents(); + (new WorldGuardBlockListener(this)).registerEvents(); + (new WorldGuardEntityListener(this)).registerEvents(); + (new WorldGuardWeatherListener(this)).registerEvents(); + (new WorldGuardVehicleListener(this)).registerEvents(); + (new WorldGuardServerListener(this)).registerEvents(); + (new WorldGuardHangingListener(this)).registerEvents(); + + // Modules + (playerMoveListener = new PlayerMoveListener(this)).registerEvents(); + (new BlacklistListener(this)).registerEvents(); + (new ChestProtectionListener(this)).registerEvents(); + (new RegionProtectionListener(this)).registerEvents(); + (new RegionFlagsListener(this)).registerEvents(); + (new WorldRulesListener(this)).registerEvents(); + (new BlockedPotionsListener(this)).registerEvents(); + (new EventAbstractionListener(this)).registerEvents(); + (new PlayerModesListener(this)).registerEvents(); + (new BuildPermissionListener(this)).registerEvents(); + (new InvincibilityListener(this)).registerEvents(); + if ("true".equalsIgnoreCase(System.getProperty("worldguard.debug.listener"))) { + (new DebuggingListener(this, WorldGuard.logger)).registerEvents(); + } + + platform.getGlobalStateManager().updateCommandBookGodMode(); + + if (getServer().getPluginManager().isPluginEnabled("CommandBook")) { + getServer().getPluginManager().registerEvents(new WorldGuardCommandBookListener(this), this); + } + + // handle worlds separately to initialize already loaded worlds + WorldGuardWorldListener worldListener = (new WorldGuardWorldListener(this)); + for (World world : getServer().getWorlds()) { + worldListener.initWorld(world); + } + worldListener.registerEvents(); + + Bukkit.getScheduler().runTask(this, () -> { + for (Player player : Bukkit.getServer().getOnlinePlayers()) { + ProcessPlayerEvent event = new ProcessPlayerEvent(player); + Events.fire(event); + } + }); + + ((SimpleFlagRegistry) WorldGuard.getInstance().getFlagRegistry()).setInitialized(true); + + // Enable metrics + final Metrics metrics = new Metrics(this, BSTATS_PLUGIN_ID); // bStats plugin id + if (platform.getGlobalStateManager().extraStats) { + setupCustomCharts(metrics); + } + } + + private void setupCustomCharts(Metrics metrics) { + metrics.addCustomChart(new SingleLineChart("region_count", () -> + platform.getRegionContainer().getLoaded().stream().mapToInt(RegionManager::size).sum())); + metrics.addCustomChart(new SimplePie("region_driver", () -> { + RegionDriver driver = platform.getGlobalStateManager().selectedRegionStoreDriver; + return driver instanceof DirectoryYamlDriver ? "yaml" : driver instanceof SQLDriver ? "sql" : "unknown"; + })); + metrics.addCustomChart(new DrilldownPie("blacklist", () -> { + int empty = 0; + Map blacklistMap = new HashMap<>(); + Map whitelistMap = new HashMap<>(); + for (BukkitWorldConfiguration worldConfig : platform.getGlobalStateManager().getWorldConfigs()) { + Blacklist blacklist = worldConfig.getBlacklist(); + if (blacklist != null && !blacklist.isEmpty()) { + Map target = blacklist.isWhitelist() ? whitelistMap : blacklistMap; + int floor = ((blacklist.getItemCount() - 1) / 10) * 10; + String range = floor >= 100 ? "101+" : (floor + 1) + " - " + (floor + 10); + target.merge(range, 1, Integer::sum); + } else { + empty++; + } + } + Map> blacklistCounts = new HashMap<>(); + Map emptyMap = new HashMap<>(); + emptyMap.put("empty", empty); + blacklistCounts.put("empty", emptyMap); + blacklistCounts.put("blacklist", blacklistMap); + blacklistCounts.put("whitelist", whitelistMap); + return blacklistCounts; + })); + metrics.addCustomChart(new SimplePie("chest_protection", () -> + "" + platform.getGlobalStateManager().getWorldConfigs().stream().anyMatch(cfg -> cfg.signChestProtection))); + metrics.addCustomChart(new SimplePie("build_permissions", () -> + "" + platform.getGlobalStateManager().getWorldConfigs().stream().anyMatch(cfg -> cfg.buildPermissions))); + + metrics.addCustomChart(new SimplePie("custom_flags", () -> + "" + (WorldGuard.getInstance().getFlagRegistry().size() > Flags.INBUILT_FLAGS.size()))); + metrics.addCustomChart(new SimplePie("custom_handlers", () -> + "" + (WorldGuard.getInstance().getPlatform().getSessionManager().customHandlersRegistered()))); + } + + @Override + public void onDisable() { + WorldGuard.getInstance().disable(); + this.getServer().getScheduler().cancelTasks(this); + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + try { + Actor actor = wrapCommandSender(sender); + try { + commands.execute(cmd.getName(), args, actor, actor); + } catch (Throwable t) { + Throwable next = t; + do { + try { + WorldGuard.getInstance().getExceptionConverter().convert(next); + } catch (org.enginehub.piston.exception.CommandException pce) { + if (pce.getCause() instanceof CommandException) { + throw ((CommandException) pce.getCause()); + } + } + next = next.getCause(); + } while (next != null); + + throw t; + } + } catch (CommandPermissionsException e) { + sender.sendMessage(ChatColor.RED + "You don't have permission."); + } catch (MissingNestedCommandException e) { + sender.sendMessage(ChatColor.RED + e.getUsage()); + } catch (CommandUsageException e) { + sender.sendMessage(ChatColor.RED + e.getMessage()); + sender.sendMessage(ChatColor.RED + e.getUsage()); + } catch (WrappedCommandException e) { + sender.sendMessage(ChatColor.RED + e.getCause().getMessage()); + } catch (CommandException e) { + sender.sendMessage(ChatColor.RED + e.getMessage()); + } + + return true; + } + + /** + * Check whether a player is in a group. + * This calls the corresponding method in PermissionsResolverManager + * + * @param player The player to check + * @param group The group + * @return whether {@code player} is in {@code group} + */ + public boolean inGroup(OfflinePlayer player, String group) { + try { + return PermissionsResolverManager.getInstance().inGroup(player, group); + } catch (Throwable t) { + t.printStackTrace(); + return false; + } + } + + /** + * Get the groups of a player. + * This calls the corresponding method in PermissionsResolverManager. + * @param player The player to check + * @return The names of each group the playe is in. + */ + public String[] getGroups(OfflinePlayer player) { + try { + return PermissionsResolverManager.getInstance().getGroups(player); + } catch (Throwable t) { + t.printStackTrace(); + return new String[0]; + } + } + + /** + * Checks permissions. + * + * @param sender The sender to check the permission on. + * @param perm The permission to check the permission on. + * @return whether {@code sender} has {@code perm} + */ + public boolean hasPermission(CommandSender sender, String perm) { + if (sender.isOp()) { + if (sender instanceof Player) { + if (platform.getGlobalStateManager().get(BukkitAdapter.adapt(((Player) sender).getWorld())).opPermissions) { + return true; + } + } else { + return true; + } + } + + // Invoke the permissions resolver + if (sender instanceof Player) { + Player player = (Player) sender; + return PermissionsResolverManager.getInstance().hasPermission(player.getWorld().getName(), player, perm); + } + + return false; + } + + /** + * Checks permissions and throws an exception if permission is not met. + * + * @param sender The sender to check the permission on. + * @param perm The permission to check the permission on. + * @throws CommandPermissionsException if {@code sender} doesn't have {@code perm} + */ + public void checkPermission(CommandSender sender, String perm) + throws CommandPermissionsException { + if (!hasPermission(sender, perm)) { + throw new CommandPermissionsException(); + } + } + + /** + * Gets a copy of the WorldEdit plugin. + * + * @return The WorldEditPlugin instance + * @throws CommandException If there is no WorldEditPlugin available + */ + public WorldEditPlugin getWorldEdit() throws CommandException { + Plugin worldEdit = getServer().getPluginManager().getPlugin("WorldEdit"); + if (worldEdit == null) { + throw new CommandException("WorldEdit does not appear to be installed."); + } else if (!worldEdit.isEnabled()) { + throw new CommandException("WorldEdit does not appear to be enabled."); + } + + if (worldEdit instanceof WorldEditPlugin) { + return (WorldEditPlugin) worldEdit; + } else { + throw new CommandException("WorldEdit detection failed (report error)."); + } + } + + /** + * Wrap a player as a LocalPlayer. + * + * @param player The player to wrap + * @return The wrapped player + */ + public LocalPlayer wrapPlayer(Player player) { + return new BukkitPlayer(this, player); + } + + /** + * Wrap a player as a LocalPlayer. + * + * @param player The player to wrap + * @param silenced True to silence messages + * @return The wrapped player + */ + public LocalPlayer wrapPlayer(Player player, boolean silenced) { + return new BukkitPlayer(this, player, silenced); + } + + public Actor wrapCommandSender(CommandSender sender) { + if (sender instanceof Player) { + return wrapPlayer((Player) sender); + } + + try { + return new BukkitCommandSender(getWorldEdit(), sender); + } catch (CommandException e) { + e.printStackTrace(); + } + return null; + } + + public CommandSender unwrapActor(Actor sender) { + if (sender instanceof BukkitPlayer) { + return ((BukkitPlayer) sender).getPlayer(); + } else if (sender instanceof BukkitCommandSender) { + return Bukkit.getConsoleSender(); // TODO Fix + } else { + throw new IllegalArgumentException("Unknown actor type. Please report"); + } + } + + /** + * Wrap a player as a LocalPlayer. + * + *

This implementation is incomplete -- permissions cannot be checked.

+ * + * @param player The player to wrap + * @return The wrapped player + */ + public LocalPlayer wrapOfflinePlayer(OfflinePlayer player) { + return new BukkitOfflinePlayer(this, player); + } + + /** + * Internal method. Do not use as API. + */ + public BukkitConfigurationManager getConfigManager() { + return platform.getGlobalStateManager(); + } + + /** + * Return a protection query helper object that can be used by another + * plugin to test whether WorldGuard permits an action at a particular + * place. + * + * @return an instance + */ + public ProtectionQuery createProtectionQuery() { + return new ProtectionQuery(); + } + + /** + * Configure WorldGuard's loggers. + */ + private void configureLogger() { + RecordMessagePrefixer.register(Logger.getLogger("com.sk89q.worldguard"), "[WorldGuard] "); + } + + /** + * Create a default configuration file from the .jar. + * + * @param actual The destination file + * @param defaultName The name of the file inside the jar's defaults folder + */ + public void createDefaultConfiguration(File actual, String defaultName) { + + // Make parent directories + File parent = actual.getParentFile(); + if (!parent.exists()) { + parent.mkdirs(); + } + + if (actual.exists()) { + return; + } + + try (InputStream stream = getResource("defaults/" + defaultName)){ + if (stream == null) throw new FileNotFoundException(); + copyDefaultConfig(stream, actual, defaultName); + } catch (IOException e) { + getLogger().severe("Unable to read default configuration: " + defaultName); + } + + } + + private void copyDefaultConfig(InputStream input, File actual, String name) { + try (FileOutputStream output = new FileOutputStream(actual)) { + byte[] buf = new byte[8192]; + int length; + while ((length = input.read(buf)) > 0) { + output.write(buf, 0, length); + } + getLogger().info("Default configuration file written: " + name); + } catch (IOException e) { + getLogger().log(Level.WARNING, "Failed to write default config file", e); + } + } + + public PlayerMoveListener getPlayerMoveListener() { + return playerMoveListener; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java new file mode 100644 index 000000000..0c9e5136c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/cause/Cause.java @@ -0,0 +1,310 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.cause; + +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import com.sk89q.worldguard.bukkit.internal.WGMetadata; +import io.papermc.lib.PaperLib; +import org.bukkit.Bukkit; +import org.bukkit.block.Block; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Creature; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.Vehicle; +import org.bukkit.metadata.Metadatable; +import org.bukkit.projectiles.BlockProjectileSource; +import org.bukkit.projectiles.ProjectileSource; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An instance of this object describes the actors that played a role in + * causing an event, with the ability to describe a situation where one actor + * controls several other actors to create the event. + * + *

For example, if a player fires an arrow that hits an item frame, the player + * is the initiator, while the arrow is merely controlled by the player to + * hit the item frame.

+ */ +public final class Cause { + + private static final String CAUSE_KEY = "worldguard.cause"; + private static final Cause UNKNOWN = new Cause(Collections.emptyList(), false); + + private final List causes; + private final boolean indirect; + + /** + * Create a new instance. + * + * @param causes a list of causes + * @param indirect whether the cause is indirect + */ + private Cause(List causes, boolean indirect) { + checkNotNull(causes); + this.causes = causes; + this.indirect = indirect; + } + + /** + * Test whether the traced cause is indirect. + * + *

If the cause is indirect, then the root cause may not be notified, + * for example.

+ * + * @return true if the cause is indirect + */ + public boolean isIndirect() { + return indirect; + } + + /** + * Return whether a cause is known. This method will return false if + * the list of causes is empty or the root cause is really not known + * (e.g. primed TNT). + * + * @return true if known + */ + public boolean isKnown() { + Object object = getRootCause(); + return !(object == null || object instanceof TNTPrimed || object instanceof Vehicle); + } + + @Nullable + public Object getRootCause() { + if (!causes.isEmpty()) { + return causes.get(0); + } + + return null; + } + + @Nullable + public Player getFirstPlayer() { + for (Object object : causes) { + if (object instanceof Player) { + return (Player) object; + } + } + + return null; + } + + @Nullable + public Entity getFirstEntity() { + for (Object object : causes) { + if (object instanceof Entity) { + return (Entity) object; + } + } + + return null; + } + + @Nullable + public Entity getFirstNonPlayerEntity() { + for (Object object : causes) { + if (object instanceof Entity && !(object instanceof Player)) { + return (Entity) object; + } + } + + return null; + } + + @Nullable + public Block getFirstBlock() { + for (Object object : causes) { + if (object instanceof Block) { + return (Block) object; + } + } + + return null; + } + + /** + * Find the first type matching one in the given array. + * + * @param types an array of types + * @return a found type or null + */ + @Nullable + public EntityType find(EntityType... types) { + for (Object object : causes) { + if (object instanceof Entity) { + for (EntityType type : types) { + if (((Entity) object).getType() == type) { + return type; + } + } + } + } + + return null; + } + + @Override + public String toString() { + return Joiner.on(" | ").join(causes); + } + + /** + * Create a new instance with the given objects as the cause, + * where the first-most object is the initial initiator and those + * following it are controlled by the previous entry. + * + * @param cause an array of causing objects + * @return a cause + */ + public static Cause create(@Nullable Object... cause) { + if (cause != null) { + Builder builder = new Builder(cause.length); + builder.addAll(cause); + return builder.build(); + } else { + return UNKNOWN; + } + } + + /** + * Create a new instance that indicates that the cause is not known. + * + * @return a cause + */ + public static Cause unknown() { + return UNKNOWN; + } + + /** + * Add a parent cause to a {@code Metadatable} object. + * + *

Note that {@code target} cannot be an instance of + * {@link Block} because {@link #create(Object...)} will not bother + * checking for such data on blocks (because it is relatively costly + * to do so).

+ * + * @param target the target + * @param parent the parent cause + * @throws IllegalArgumentException thrown if {@code target} is an instance of {@link Block} + */ + public static void trackParentCause(Metadatable target, Object parent) { + if (target instanceof Block) { + throw new IllegalArgumentException("Can't track causes on Blocks because Cause doesn't check block metadata"); + } + + WGMetadata.put(target, CAUSE_KEY, parent); + } + + /** + * Remove a parent cause from a {@code Metadatable} object. + * + * @param target the target + */ + public static void untrackParentCause(Metadatable target) { + WGMetadata.remove(target, CAUSE_KEY); + } + + /** + * Builds causes. + */ + private static final class Builder { + private final List causes; + private final Set seen = Sets.newHashSet(); + private boolean indirect; + + private Builder(int expectedSize) { + this.causes = new ArrayList<>(expectedSize); + } + + private void addAll(@Nullable Object... element) { + if (element != null) { + for (Object o : element) { + if (o == null || seen.contains(o)) { + continue; + } + + seen.add(o); + + if (o instanceof TNTPrimed) { + addAll(((TNTPrimed) o).getSource()); + } else if (o instanceof Projectile) { + ProjectileSource shooter = ((Projectile) o).getShooter(); + addAll(shooter); + if (shooter == null && o instanceof Firework && PaperLib.isPaper()) { + UUID spawningUUID = ((Firework) o).getSpawningEntity(); + if (spawningUUID != null) { + Entity spawningEntity = Bukkit.getEntity(spawningUUID); + if (spawningEntity != null) { + addAll(spawningEntity); + } + } + } + } else if (o instanceof Vehicle) { + ((Vehicle) o).getPassengers().forEach(this::addAll); + } else if (o instanceof AreaEffectCloud) { + indirect = true; + addAll(((AreaEffectCloud) o).getSource()); + } else if (o instanceof Tameable) { + indirect = true; + addAll(((Tameable) o).getOwner()); + } else if (o instanceof Creature && ((Creature) o).getTarget() != null) { + indirect = true; + addAll(((Creature) o).getTarget()); + } else if (o instanceof BlockProjectileSource) { + addAll(((BlockProjectileSource) o).getBlock()); + } + + // Add manually tracked parent causes + Object source = o; + int index = causes.size(); + while (source instanceof Metadatable && !(source instanceof Block)) { + source = WGMetadata.getIfPresent((Metadatable) source, CAUSE_KEY, Object.class); + if (source != null) { + causes.add(index, source); + seen.add(source); + } + } + + causes.add(o); + } + } + } + + public Cause build() { + return new Cause(causes, indirect); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/chest/BukkitSignChestProtection.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/chest/BukkitSignChestProtection.java new file mode 100644 index 000000000..b832e990b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/chest/BukkitSignChestProtection.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.chest; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.chest.SignChestProtection; +import org.bukkit.block.BlockState; +import org.bukkit.block.Sign; + +public class BukkitSignChestProtection extends SignChestProtection { + + private Boolean isProtectedSign(Sign sign, LocalPlayer player) { + if (sign.getLine(0).equalsIgnoreCase("[Lock]")) { + if (player == null) { // No player, no access + return true; + } + + String name = player.getName(); + return !name.equalsIgnoreCase(sign.getLine(1).trim()) + && !name.equalsIgnoreCase(sign.getLine(2).trim()) + && !name.equalsIgnoreCase(sign.getLine(3).trim()); + } + + return null; + } + + @Override + public Boolean isProtectedSign(Location block, LocalPlayer player) { + BlockState state = BukkitAdapter.adapt(block).getBlock().getState(); + if (!(state instanceof Sign)) { + return null; + } + return isProtectedSign((Sign) state, player); + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/BulkEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/BulkEvent.java new file mode 100644 index 000000000..5675a7438 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/BulkEvent.java @@ -0,0 +1,39 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event; + +import org.bukkit.event.Event.Result; + +/** + * A bulk event contains several affected objects in a list. + */ +public interface BulkEvent { + + /** + * Get the actual result. + * + *

By default, bulk events will set the result to DENY if the number of + * affected objects drops to zero. This method returns the true result.

+ * + * @return the explicit result + */ + Result getExplicitResult(); + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvent.java new file mode 100644 index 000000000..12e676bde --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvent.java @@ -0,0 +1,142 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event; + +import com.google.common.collect.Lists; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.protection.flags.StateFlag; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +import javax.annotation.Nullable; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + */ +public abstract class DelegateEvent extends Event implements Cancellable, Handleable { + + @Nullable + private final Event originalEvent; + private final Cause cause; + private final List relevantFlags = Lists.newArrayList(); + private Result result = Result.DEFAULT; + private boolean silent; + + /** + * Create a new instance + * + * @param originalEvent the original event + * @param cause the cause + */ + protected DelegateEvent(@Nullable Event originalEvent, Cause cause) { + checkNotNull(cause); + this.originalEvent = originalEvent; + this.cause = cause; + } + + /** + * Get the original event. + * + * @return the original event, which may be {@code null} if unavailable + */ + @Nullable + public Event getOriginalEvent() { + return originalEvent; + } + + /** + * Return the cause. + * + * @return the cause + */ + public Cause getCause() { + return cause; + } + + /** + * Get a list of relevant flags to consider for this event. + * + * @return A list of relevant flags + */ + public List getRelevantFlags() { + return relevantFlags; + } + + @Override + public boolean isCancelled() { + return getResult() == Result.DENY; + } + + @Override + public void setCancelled(boolean cancel) { + if (cancel) { + setResult(Result.DENY); + } + } + + @Override + public Result getResult() { + return result; + } + + @Override + public void setResult(Result result) { + this.result = result; + } + + /** + * Get whether this should be a silent check. + * + * @return true if a silent check + */ + public boolean isSilent() { + return silent; + } + + /** + * Set whether this should be a silent check. + * + * @param silent true if silent + * @return the same event + */ + public DelegateEvent setSilent(boolean silent) { + this.silent = silent; + return this; + } + + /** + * Set the event to {@link org.bukkit.event.Event.Result#ALLOW} if {@code allowed} is true. + * + * @param allowed true to set the result + * @return the same event + */ + public DelegateEvent setAllowed(boolean allowed) { + if (allowed) { + setResult(Result.ALLOW); + } + + return this; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvents.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvents.java new file mode 100644 index 000000000..92379cf2b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/DelegateEvents.java @@ -0,0 +1,72 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event; + +import org.bukkit.event.Event.Result; + +/** + * Utility methods for dealing with delegate events. + */ +public final class DelegateEvents { + + private DelegateEvents() { + } + + /** + * Set an event to be silent. + * + * @param event the event + * @param the type of event + * @return the same event + */ + public static T setSilent(T event) { + event.setSilent(true); + return event; + } + + /** + * Set an event to be silent. + * + * @param event the event + * @param silent true to set silent + * @param the type of event + * @return the same event + */ + public static T setSilent(T event, boolean silent) { + event.setSilent(silent); + return event; + } + + /** + * Set an event as handled as {@link Result#ALLOW} if {@code allowed} is + * true, otherwise do nothing. + * + * @param event the event + * @param the type of event + * @return the same event + */ + public static T setAllowed(T event, boolean allowed) { + if (allowed) { + event.setResult(Result.ALLOW); + } + return event; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/Handleable.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/Handleable.java new file mode 100644 index 000000000..f521287ae --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/Handleable.java @@ -0,0 +1,30 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event; + +import org.bukkit.event.Event.Result; + +public interface Handleable { + + Result getResult(); + + void setResult(Result result); + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/AbstractBlockEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/AbstractBlockEvent.java new file mode 100644 index 000000000..8e1192523 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/AbstractBlockEvent.java @@ -0,0 +1,186 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.block; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.BulkEvent; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Event; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + */ +abstract class AbstractBlockEvent extends DelegateEvent implements BulkEvent { + + private final World world; + private List blocks; + private final List blockStates; + private final Material effectiveMaterial; + + protected AbstractBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks, Material effectiveMaterial) { + super(originalEvent, cause); + checkNotNull(world); + checkNotNull(blocks); + checkNotNull(effectiveMaterial); + this.world = world; + this.blocks = blocks; + this.effectiveMaterial = effectiveMaterial; + this.blockStates = null; + } + + protected AbstractBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks) { + super(originalEvent, cause); + checkNotNull(world); + checkNotNull(blocks); + checkArgument(!blocks.isEmpty()); + this.world = world; + this.blockStates = blocks; + this.blocks = null; + this.effectiveMaterial = blocks.get(0).getType(); + } + + protected AbstractBlockEvent(@Nullable Event originalEvent, Cause cause, Block block) { + this(originalEvent, cause, block.getWorld(), createList(checkNotNull(block)), block.getType()); + } + + protected AbstractBlockEvent(@Nullable Event originalEvent, Cause cause, Location target, Material effectiveMaterial) { + this(originalEvent, cause, target.getWorld(), createList(target.getBlock()), effectiveMaterial); + } + + private static List createList(Block block) { + List blocks = new ArrayList<>(); + blocks.add(block); + return blocks; + } + + /** + * Get the world. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the affected blocks. + * + * @return a list of affected block + */ + public List getBlocks() { + if (blocks == null) { // be lazy here because we often don't call getBlocks internally, just filter + blocks = blockStates.stream().map(BlockState::getBlock).collect(Collectors.toList()); + } + return blocks; + } + + /** + * Filter the list of affected blocks with the given predicate. If the + * predicate returns {@code false}, then the block is removed. + * + * @param predicate the predicate + * @param cancelEventOnFalse true to cancel the event and clear the block + * list once the predicate returns {@code false} + * @return true if one or more blocks were filtered out + */ + public boolean filter(Predicate predicate, boolean cancelEventOnFalse) { + return blocks == null + ? filterInternal(blockStates, BlockState::getLocation, predicate, cancelEventOnFalse) + : filterInternal(blocks, Block::getLocation, predicate, cancelEventOnFalse); + } + + private boolean filterInternal(List blockList, Function locFunc, + Predicate predicate, boolean cancelEventOnFalse) { + boolean hasRemoval = false; + Iterator it = blockList.iterator(); + while (it.hasNext()) { + if (!predicate.test(locFunc.apply(it.next()))) { + hasRemoval = true; + + if (cancelEventOnFalse) { + blockList.clear(); + setCancelled(true); + break; + } else { + it.remove(); + } + } + } + return hasRemoval; + } + + /** + * Filter the list of affected blocks with the given predicate. If the + * predicate returns {@code false}, then the block is removed. + * + *

This method will not fail fast and + * cancel the event the first instance that the predicate returns + * {@code false}. See {@link #filter(Predicate, boolean)} to adjust + * this behavior.

+ * + * @param predicate the predicate + * @return true if one or more blocks were filtered out + */ + public boolean filter(Predicate predicate) { + return filter(predicate, false); + } + + /** + * Get the effective material of the block, regardless of what the block + * currently is. + * + * @return the effective material + */ + public Material getEffectiveMaterial() { + return effectiveMaterial; + } + + @Override + public Result getResult() { + if (blocks == null ? blockStates.isEmpty() : blocks.isEmpty()) { + return Result.DENY; + } + return super.getResult(); + } + + @Override + public Result getExplicitResult() { + return super.getResult(); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/BreakBlockEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/BreakBlockEvent.java new file mode 100644 index 000000000..2f039a62b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/BreakBlockEvent.java @@ -0,0 +1,64 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.block; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when a block is broken.

+ */ +public class BreakBlockEvent extends AbstractBlockEvent { + + private static final HandlerList handlers = new HandlerList(); + + public BreakBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks, Material effectiveMaterial) { + super(originalEvent, cause, world, blocks, effectiveMaterial); + } + + public BreakBlockEvent(@Nullable Event originalEvent, Cause cause, Block block) { + super(originalEvent, cause, block); + } + + public BreakBlockEvent(@Nullable Event originalEvent, Cause cause, Location target, Material effectiveMaterial) { + super(originalEvent, cause, target, effectiveMaterial); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/PlaceBlockEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/PlaceBlockEvent.java new file mode 100644 index 000000000..92ce620ae --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/PlaceBlockEvent.java @@ -0,0 +1,69 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.block; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when a block is placed.

+ */ +public class PlaceBlockEvent extends AbstractBlockEvent { + + private static final HandlerList handlers = new HandlerList(); + + public PlaceBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks) { + super(originalEvent, cause, world, blocks); + } + + public PlaceBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks, Material effectiveMaterial) { + super(originalEvent, cause, world, blocks, effectiveMaterial); + } + + public PlaceBlockEvent(@Nullable Event originalEvent, Cause cause, Block block) { + super(originalEvent, cause, block); + } + + public PlaceBlockEvent(@Nullable Event originalEvent, Cause cause, Location target, Material effectiveMaterial) { + super(originalEvent, cause, target, effectiveMaterial); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/UseBlockEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/UseBlockEvent.java new file mode 100644 index 000000000..31d17c248 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/block/UseBlockEvent.java @@ -0,0 +1,64 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.block; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when a block is interacted with.

+ */ +public class UseBlockEvent extends AbstractBlockEvent { + + private static final HandlerList handlers = new HandlerList(); + + public UseBlockEvent(@Nullable Event originalEvent, Cause cause, World world, List blocks, Material effectiveMaterial) { + super(originalEvent, cause, world, blocks, effectiveMaterial); + } + + public UseBlockEvent(@Nullable Event originalEvent, Cause cause, Block block) { + super(originalEvent, cause, block); + } + + public UseBlockEvent(@Nullable Event originalEvent, Cause cause, Location target, Material effectiveMaterial) { + super(originalEvent, cause, target, effectiveMaterial); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelAttempt.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelAttempt.java new file mode 100644 index 000000000..a99dd3e79 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelAttempt.java @@ -0,0 +1,76 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.event.Cancellable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Represents call to {@link Cancellable#setCancelled(boolean)}. + */ +public class CancelAttempt { + + private final boolean before; + private final boolean after; + private final StackTraceElement[] stackTrace; + + /** + * Create a new instance. + * + * @param before The cancellation flag before the call + * @param after The cancellation flag after the call + * @param stackTrace The stack trace + */ + public CancelAttempt(boolean before, boolean after, StackTraceElement[] stackTrace) { + checkNotNull(stackTrace, "stackTrace"); + this.before = before; + this.after = after; + this.stackTrace = stackTrace; + } + + /** + * Get the cancellation state before the call. + * + * @return Whether the event was cancelled before + */ + public boolean getBefore() { + return before; + } + + /** + * Get the cancellation state after the call. + * + * @return The new cancellation state + */ + public boolean getAfter() { + return after; + } + + /** + * Get the stack trace. + * + * @return The stack trace + */ + public StackTraceElement[] getStackTrace() { + return stackTrace; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogger.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogger.java new file mode 100644 index 000000000..42c67345a --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogger.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.List; + +/** + * Logs attempts at cancellation. + */ +public class CancelLogger { + + private List entries = new ArrayList<>(); + + /** + * Log a call. + * + * @param before The cancellation flag before the call + * @param after The cancellation flag after the call + * @param stackTrace The stack trace + */ + public void log(boolean before, boolean after, StackTraceElement[] stackTrace) { + entries.add(new CancelAttempt(before, after, stackTrace)); + } + + /** + * Get an immutable list of cancels. + * + * @return An immutable list + */ + public List getCancels() { + return ImmutableList.copyOf(entries); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogging.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogging.java new file mode 100644 index 000000000..b104f230c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/CancelLogging.java @@ -0,0 +1,35 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.event.Cancellable; + +import java.util.List; + +public interface CancelLogging extends Cancellable { + + /** + * Get an immutable list of cancels. + * + * @return An immutable list + */ + List getCancels(); + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockBreakEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockBreakEvent.java new file mode 100644 index 000000000..4d44d6f8b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockBreakEvent.java @@ -0,0 +1,46 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; + +import java.util.List; + +public class LoggingBlockBreakEvent extends BlockBreakEvent implements CancelLogging { + + private final CancelLogger logger = new CancelLogger(); + + public LoggingBlockBreakEvent(Block block, Player player) { + super(block, player); + } + + public List getCancels() { + return logger.getCancels(); + } + + @Override + public void setCancelled(boolean cancel) { + this.logger.log(isCancelled(), cancel, new Exception().getStackTrace()); + super.setCancelled(cancel); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockPlaceEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockPlaceEvent.java new file mode 100644 index 000000000..7653df1d2 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingBlockPlaceEvent.java @@ -0,0 +1,48 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class LoggingBlockPlaceEvent extends BlockPlaceEvent implements CancelLogging { + + private final CancelLogger logger = new CancelLogger(); + + public LoggingBlockPlaceEvent(Block placedBlock, BlockState replacedBlockState, Block placedAgainst, ItemStack itemInHand, Player thePlayer, boolean canBuild) { + super(placedBlock, replacedBlockState, placedAgainst, itemInHand, thePlayer, canBuild); + } + + public List getCancels() { + return logger.getCancels(); + } + + @Override + public void setCancelled(boolean cancel) { + this.logger.log(isCancelled(), cancel, new Exception().getStackTrace()); + super.setCancelled(cancel); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingEntityDamageByEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingEntityDamageByEntityEvent.java new file mode 100644 index 000000000..178506fe5 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingEntityDamageByEntityEvent.java @@ -0,0 +1,45 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.entity.Entity; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +import java.util.List; + +public class LoggingEntityDamageByEntityEvent extends EntityDamageByEntityEvent implements CancelLogging { + + private final CancelLogger logger = new CancelLogger(); + + public LoggingEntityDamageByEntityEvent(Entity damager, Entity damagee, DamageCause cause, double damage) { + super(damager, damagee, cause, damage); + } + + public List getCancels() { + return logger.getCancels(); + } + + @Override + public void setCancelled(boolean cancel) { + this.logger.log(isCancelled(), cancel, new Exception().getStackTrace()); + super.setCancelled(cancel); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingPlayerInteractEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingPlayerInteractEvent.java new file mode 100644 index 000000000..b958b1101 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/debug/LoggingPlayerInteractEvent.java @@ -0,0 +1,62 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.debug; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Player; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class LoggingPlayerInteractEvent extends PlayerInteractEvent implements CancelLogging { + + private final CancelLogger logger = new CancelLogger(); + + public LoggingPlayerInteractEvent(Player who, Action action, ItemStack item, Block clickedBlock, BlockFace clickedFace) { + super(who, action, item, clickedBlock, clickedFace); + } + + @Override + public List getCancels() { + return logger.getCancels(); + } + + @Override + public void setUseInteractedBlock(Result useInteractedBlock) { + this.logger.log(useInteractedBlock() == Result.DENY, useInteractedBlock == Result.DENY, new Exception().getStackTrace()); + super.setUseInteractedBlock(useInteractedBlock); + } + + @Override + public void setUseItemInHand(Result useItemInHand) { + this.logger.log(useItemInHand() == Result.DENY, useItemInHand == Result.DENY, new Exception().getStackTrace()); + super.setUseItemInHand(useItemInHand); + } + + @Override + public void setCancelled(boolean cancel) { + this.logger.log(isCancelled(), cancel, new Exception().getStackTrace()); + super.setCancelled(cancel); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/AbstractEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/AbstractEntityEvent.java new file mode 100644 index 000000000..ee0b96c51 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/AbstractEntityEvent.java @@ -0,0 +1,106 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.entity; + +import com.google.common.base.Predicate; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + */ +abstract class AbstractEntityEvent extends DelegateEvent { + + private final Location target; + @Nullable + private final Entity entity; + + protected AbstractEntityEvent(@Nullable Event originalEvent, Cause cause, Entity entity) { + super(originalEvent, cause); + checkNotNull(entity); + this.target = entity.getLocation(); + this.entity = entity; + } + + protected AbstractEntityEvent(@Nullable Event originalEvent, Cause cause, Location target) { + super(originalEvent, cause); + checkNotNull(target); + this.target = target; + this.entity = null; + } + + /** + * Get the world. + * + * @return the world + */ + public World getWorld() { + return target.getWorld(); + } + + /** + * Get the target location being affected. + * + * @return a location + */ + public Location getTarget() { + return target; + } + + + /** + * Get the target entity being affected. + * + * @return a entity + */ + @Nullable + public Entity getEntity() { + return entity; + } + + /** + * Filter the list of affected entities with the given predicate. If the + * predicate returns {@code false}, then the entity is not affected. + * + * @param predicate the predicate + * @param cancelEventOnFalse true to cancel the event and clear the entity + * list once the predicate returns {@code false} + * @return true if one or more entities were filtered out + */ + public boolean filter(Predicate predicate, boolean cancelEventOnFalse) { + if (!isCancelled()) { + if (!predicate.apply(getTarget())) { + setCancelled(true); + } + } + + return isCancelled(); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DamageEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DamageEntityEvent.java new file mode 100644 index 000000000..cef3342b8 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DamageEntityEvent.java @@ -0,0 +1,61 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.entity; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when an entity is damaged.

+ */ +public class DamageEntityEvent extends AbstractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + + public DamageEntityEvent(@Nullable Event originalEvent, Cause cause, Entity target) { + super(originalEvent, cause, checkNotNull(target)); + } + + @Override + @Nonnull + public Entity getEntity() { + return super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DestroyEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DestroyEntityEvent.java new file mode 100644 index 000000000..bcf744d95 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/DestroyEntityEvent.java @@ -0,0 +1,61 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.entity; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when an entity is removed.

+ */ +public class DestroyEntityEvent extends AbstractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + + public DestroyEntityEvent(@Nullable Event originalEvent, Cause cause, Entity target) { + super(originalEvent, cause, checkNotNull(target)); + } + + @Override + @Nonnull + public Entity getEntity() { + return super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/SpawnEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/SpawnEntityEvent.java new file mode 100644 index 000000000..0228024ac --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/SpawnEntityEvent.java @@ -0,0 +1,73 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.entity; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when an entity is spawned.

+ */ +public class SpawnEntityEvent extends AbstractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + private final EntityType effectiveType; + + public SpawnEntityEvent(@Nullable Event originalEvent, Cause cause, Entity target) { + super(originalEvent, cause, checkNotNull(target)); + this.effectiveType = target.getType(); + } + + public SpawnEntityEvent(@Nullable Event originalEvent, Cause cause, Location location, EntityType type) { + super(originalEvent, cause, location); + checkNotNull(type); + this.effectiveType = type; + } + + /** + * Get the effective entity type of the spawned entity. + * + * @return the effective type + */ + public EntityType getEffectiveType() { + return effectiveType; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/UseEntityEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/UseEntityEvent.java new file mode 100644 index 000000000..c54a9fcd7 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/entity/UseEntityEvent.java @@ -0,0 +1,61 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.entity; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import org.bukkit.entity.Entity; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when an entity is used.

+ */ +public class UseEntityEvent extends AbstractEntityEvent { + + private static final HandlerList handlers = new HandlerList(); + + public UseEntityEvent(@Nullable Event originalEvent, Cause cause, Entity target) { + super(originalEvent, cause, checkNotNull(target)); + } + + @Override + @Nonnull + public Entity getEntity() { + return super.getEntity(); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/inventory/UseItemEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/inventory/UseItemEvent.java new file mode 100644 index 000000000..d387e00f2 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/inventory/UseItemEvent.java @@ -0,0 +1,80 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.inventory; + +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import org.bukkit.World; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Thrown when an item is used.

+ */ +public class UseItemEvent extends DelegateEvent { + + private static final HandlerList handlers = new HandlerList(); + private final World world; + private final ItemStack itemStack; + + public UseItemEvent(@Nullable Event originalEvent, Cause cause, World world, ItemStack itemStack) { + super(originalEvent, cause); + checkNotNull(world); + checkNotNull(itemStack); + this.world = world; + this.itemStack = itemStack; + } + + /** + * Get the world. + * + * @return the world + */ + public World getWorld() { + return world; + } + + /** + * Get the item stack. + * + * @return the item stack + */ + public ItemStack getItemStack() { + return itemStack; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/player/ProcessPlayerEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/player/ProcessPlayerEvent.java new file mode 100644 index 000000000..af50ae4e8 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/event/player/ProcessPlayerEvent.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.event.player; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This event is an internal event. We do not recommend handling or throwing + * this event or its subclasses as the interface is highly subject to change. + * + *

Posted when a player has to be processed, either because a world has been + * loaded, the server has started, or WorldGuard has been reloaded.

+ */ +public class ProcessPlayerEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + + public ProcessPlayerEvent(Player player) { + checkNotNull(player); + this.player = player; + } + + public Player getPlayer() { + return player; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/TargetMatcherSet.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/TargetMatcherSet.java new file mode 100644 index 000000000..ce2562e7e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/TargetMatcherSet.java @@ -0,0 +1,84 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.internal; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.blacklist.target.BlockTarget; +import com.sk89q.worldguard.blacklist.target.ItemTarget; +import com.sk89q.worldguard.blacklist.target.Target; +import com.sk89q.worldguard.blacklist.target.TargetMatcher; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.inventory.ItemStack; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class TargetMatcherSet { + + private final Multimap entries = HashMultimap.create(); + + public boolean add(TargetMatcher matcher) { + checkNotNull(matcher); + return entries.put(matcher.getMatchedTypeId(), matcher); + } + + public boolean test(Target target) { + Collection matchers = entries.get(target.getTypeId()); + + for (TargetMatcher matcher : matchers) { + if (matcher.test(target)) { + return true; + } + } + + return false; + } + + public boolean test(Material material) { + if (material.isBlock()) { + return test(new BlockTarget(BukkitAdapter.asBlockType(material))); + } else { + return test(new ItemTarget(BukkitAdapter.asItemType(material))); + } + } + + public boolean test(Block block) { + return test(new BlockTarget(BukkitAdapter.asBlockType(block.getType()))); + } + + public boolean test(BlockState state) { + return test(new BlockTarget(BukkitAdapter.asBlockType(state.getType()))); + } + + public boolean test(ItemStack itemStack) { + return test(new ItemTarget(BukkitAdapter.asItemType(itemStack.getType()))); + } + + @Override + public String toString() { + return entries.toString(); + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/WGMetadata.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/WGMetadata.java new file mode 100644 index 000000000..99acd932b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/internal/WGMetadata.java @@ -0,0 +1,87 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.internal; + +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.metadata.Metadatable; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * Utility methods for dealing with metadata on entities. + * + *

WorldGuard is placed as the owner of all values.

+ */ +public final class WGMetadata { + + private WGMetadata() { + } + + /** + * Add some metadata to a target. + * + * @param target the target + * @param key the key + * @param value the value + */ + public static void put(Metadatable target, String key, Object value) { + target.setMetadata(key, new FixedMetadataValue(WorldGuardPlugin.inst(), value)); + } + + /** + * Get the (first) metadata value on the given target that has the given + * key and is of the given class type. + * + * @param target the target + * @param key the key + * @param expected the type of the value + * @param the type of the value + * @return a value, or {@code null} if one does not exists + */ + @Nullable + @SuppressWarnings("unchecked") + public static T getIfPresent(Metadatable target, String key, Class expected) { + List values = target.getMetadata(key); + WorldGuardPlugin owner = WorldGuardPlugin.inst(); + for (MetadataValue value : values) { + if (value.getOwningPlugin() == owner) { + Object v = value.value(); + if (expected.isInstance(v)) { + return (T) v; + } + } + } + + return null; + } + + /** + * Removes metadata from the target. + * + * @param target the target + * @param key the key + */ + public static void remove(Metadatable target, String key) { + target.removeMetadata(key, WorldGuardPlugin.inst()); + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/AbstractListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/AbstractListener.java new file mode 100644 index 000000000..169d0c698 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/AbstractListener.java @@ -0,0 +1,155 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.BukkitConfigurationManager; +import com.sk89q.worldguard.bukkit.BukkitPlayer; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.association.DelayedRegionOverlapAssociation; +import com.sk89q.worldguard.protection.association.Associables; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import io.papermc.lib.PaperLib; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; + +/** + * Abstract listener to ease creation of listeners. + */ +class AbstractListener implements Listener { + + private final WorldGuardPlugin plugin; + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public AbstractListener(WorldGuardPlugin plugin) { + checkNotNull(plugin); + this.plugin = plugin; + } + + /** + * Register events. + */ + public void registerEvents() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + /** + * Get the plugin. + * + * @return the plugin + */ + protected static WorldGuardPlugin getPlugin() { + return WorldGuardPlugin.inst(); + } + + /** + * Get the global configuration. + * + * @return the configuration + */ + protected static BukkitConfigurationManager getConfig() { + return getPlugin().getConfigManager(); + } + + /** + * Get the world configuration given a world. + * + * @param world The world to get the configuration for. + * @return The configuration for {@code world} + */ + protected static BukkitWorldConfiguration getWorldConfig(String world) { + return getConfig().get(world); + } + + protected static BukkitWorldConfiguration getWorldConfig(org.bukkit.World world) { + return getWorldConfig(world.getName()); + } + + /** + * Get the world configuration given a player. + * + * @param player The player to get the wold from + * @return The {@link WorldConfiguration} for the player's world + */ + protected static BukkitWorldConfiguration getWorldConfig(LocalPlayer player) { + return getWorldConfig(((BukkitPlayer) player).getPlayer().getWorld()); + } + + /** + * Return whether region support is enabled. + * + * @param world the world + * @return true if region support is enabled + */ + protected static boolean isRegionSupportEnabled(org.bukkit.World world) { + return getWorldConfig(world).useRegions; + } + + protected RegionAssociable createRegionAssociable(Cause cause) { + Object rootCause = cause.getRootCause(); + + if (!cause.isKnown()) { + return Associables.constant(Association.NON_MEMBER); + } else if (rootCause instanceof Player) { + return getPlugin().wrapPlayer((Player) rootCause); + } else if (rootCause instanceof OfflinePlayer) { + return getPlugin().wrapOfflinePlayer((OfflinePlayer) rootCause); + } else if (rootCause instanceof Entity) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + final Entity entity = (Entity) rootCause; + BukkitWorldConfiguration config = getWorldConfig(entity.getWorld()); + Location loc; + if (PaperLib.isPaper() && config.usePaperEntityOrigin) { + loc = entity.getOrigin(); + if (loc == null) { + loc = entity.getLocation(); + } + } else { + loc = entity.getLocation(); + } + return new DelayedRegionOverlapAssociation(query, BukkitAdapter.adapt(loc), + config.useMaxPriorityAssociation); + } else if (rootCause instanceof Block) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + Location loc = ((Block) rootCause).getLocation(); + return new DelayedRegionOverlapAssociation(query, BukkitAdapter.adapt(loc), + getWorldConfig(loc.getWorld()).useMaxPriorityAssociation); + } else { + return Associables.constant(Association.NON_MEMBER); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlacklistListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlacklistListener.java new file mode 100644 index 000000000..bfb569f5a --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlacklistListener.java @@ -0,0 +1,390 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.blacklist.event.BlockBreakBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.BlockDispenseBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.BlockInteractBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.BlockPlaceBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.ItemAcquireBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.ItemDestroyWithBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.ItemDropBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.ItemEquipBlacklistEvent; +import com.sk89q.worldguard.blacklist.event.ItemUseBlacklistEvent; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Item; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.BlockDispenseArmorEvent; +import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCreativeEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; + +import static com.sk89q.worldguard.bukkit.BukkitUtil.createTarget; + +/** + * Handle events that need to be processed by the blacklist. + */ +public class BlacklistListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public BlacklistListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakBlock(final BreakBlockEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + event.filter(target -> { + if (!wcfg.getBlacklist().check( + new BlockBreakBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(target), + createTarget(target.getBlock(), event.getEffectiveMaterial())), false, false)) { + return false; + } else if (!wcfg.getBlacklist().check( + new ItemDestroyWithBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(target), + createTarget(player.getInventory().getItemInMainHand())), false, false)) { + return false; + } + + return true; + }); + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceBlock(final PlaceBlockEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + event.filter(target -> wcfg.getBlacklist().check(new BlockPlaceBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(target), createTarget(target.getBlock(), event.getEffectiveMaterial())), false, false)); + } + + @EventHandler(ignoreCancelled = true) + public void onUseBlock(final UseBlockEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + event.filter(target -> wcfg.getBlacklist().check(new BlockInteractBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(target), createTarget(target.getBlock(), event.getEffectiveMaterial())), false, false)); + } + + @EventHandler(ignoreCancelled = true) + public void onSpawnEntity(SpawnEntityEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + Material material = Materials.getRelatedMaterial(event.getEffectiveType()); + if (material != null) { + if (!wcfg.getBlacklist().check(new ItemUseBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(event.getTarget()), createTarget(material)), false, false)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onDestroyEntity(DestroyEntityEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + Entity target = event.getEntity(); + if (target instanceof Item) { + Item item = (Item) target; + if (!wcfg.getBlacklist().check( + new ItemAcquireBlacklistEvent(localPlayer, + BukkitAdapter.asBlockVector(target.getLocation()), createTarget(item.getItemStack())), false, true)) { + event.setCancelled(true); + return; + } + } + + Material material = Materials.getRelatedMaterial(target.getType()); + if (material != null) { + // Not really a block but we only have one on-break blacklist event + if (!wcfg.getBlacklist().check(new BlockBreakBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(event.getTarget()), createTarget(material)), false, false)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseItem(UseItemEvent event) { + final WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Blacklist guard + if (wcfg.getBlacklist() == null) { + return; + } + Player player = event.getCause().getFirstPlayer(); + + if (player == null) { + return; + } + + final LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + ItemStack target = event.getItemStack(); + if (!wcfg.getBlacklist().check(new ItemUseBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(player.getLocation()), createTarget(target)), false, false)) { + event.setCancelled(true); + return; + } + + if (Materials.isArmor(target.getType()) && !wcfg.getBlacklist().check(new ItemEquipBlacklistEvent( + localPlayer, BukkitAdapter.asBlockVector(player.getLocation()), createTarget(target)), false, false)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerDropItem(PlayerDropItemEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getPlayer().getWorld()); + + if (wcfg.getBlacklist() != null) { + Item ci = event.getItemDrop(); + + if (!wcfg.getBlacklist().check( + new ItemDropBlacklistEvent(getPlugin().wrapPlayer(event.getPlayer()), + BukkitAdapter.asBlockVector(ci.getLocation()), createTarget(ci.getItemStack())), false, false)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockDispense(BlockDispenseEvent event) { + BukkitWorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + if (wcfg.getBlacklist() != null) { + if (!wcfg.getBlacklist().check(new BlockDispenseBlacklistEvent(null, BukkitAdapter.asBlockVector(event.getBlock().getLocation()), + createTarget(event.getItem())), false, false)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onInventoryClick(InventoryClickEvent event) { + HumanEntity entity = event.getWhoClicked(); + if (!(entity instanceof Player)) return; + Inventory inventory = event.getInventory(); + ItemStack item = event.getCurrentItem(); + + if (item != null && inventory.getHolder() != null) { + Player player = (Player) entity; + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check( + new ItemAcquireBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(entity.getLocation()), createTarget(item)), false, false)) { + event.setCancelled(true); + + if (inventory.getHolder().equals(player)) { + event.setCurrentItem(null); + } + } + + + ItemStack equipped = checkEquipped(event); + if (equipped != null) { + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check(new ItemEquipBlacklistEvent(localPlayer, + BukkitAdapter.asBlockVector(player.getLocation()), createTarget(equipped)), false, false)) { + event.setCancelled(true); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onInventoryDrag(InventoryDragEvent event) { + HumanEntity entity = event.getWhoClicked(); + if (!(entity instanceof Player)) return; + if (event.getInventory().getType() != InventoryType.PLAYER + && event.getInventory().getType() != InventoryType.CRAFTING) return; + if (event.getRawSlots().stream().anyMatch(i -> i >= 5 && i <= 8)) { // dropped on armor slots + Player player = (Player) entity; + ConfigurationManager cfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + WorldConfiguration wcfg = cfg.get(BukkitAdapter.adapt(entity.getWorld())); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check(new ItemEquipBlacklistEvent(localPlayer, + BukkitAdapter.asBlockVector(player.getLocation()), createTarget(event.getOldCursor())), false, false)) { + event.setCancelled(true); + } + } + } + + private ItemStack checkEquipped(InventoryClickEvent event) { + final Inventory clickedInventory = event.getClickedInventory(); + if (event.getSlotType() == InventoryType.SlotType.ARMOR) { + switch (event.getAction()) { + case PLACE_ONE: + case PLACE_SOME: + case PLACE_ALL: + case SWAP_WITH_CURSOR: + final ItemStack cursor = event.getCursor(); + if (cursor != null) { + return cursor; + } + case HOTBAR_SWAP: + if (event.getClick() == ClickType.SWAP_OFFHAND) { + return clickedInventory == null ? null : ((PlayerInventory) clickedInventory).getItemInOffHand(); + } + return clickedInventory == null ? null : clickedInventory.getItem(event.getHotbarButton()); + default: + break; + } + } else if (clickedInventory != null && clickedInventory.getType() == InventoryType.PLAYER + && (event.getView().getTopInventory().getType() == InventoryType.PLAYER + || event.getView().getTopInventory().getType() == InventoryType.CRAFTING) + && event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY) { + return event.getCurrentItem(); + } + return null; + } + + @EventHandler(ignoreCancelled = true) + public void onInventoryCreative(InventoryCreativeEvent event) { + HumanEntity entity = event.getWhoClicked(); + ItemStack item = event.getCursor(); + + if (item.getType() != Material.AIR && entity instanceof Player) { + Player player = (Player) entity; + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check( + new ItemAcquireBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(entity.getLocation()), createTarget(item)), false, false)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerItemHeld(PlayerItemHeldEvent event) { + Player player = event.getPlayer(); + Inventory inventory = player.getInventory(); + ItemStack item = inventory.getItem(event.getNewSlot()); + + if (item != null) { + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check( + new ItemAcquireBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(player.getLocation()), createTarget(item)), false, false)) { + inventory.setItem(event.getNewSlot(), null); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockDispenseArmor(BlockDispenseArmorEvent event) { + if (!(event.getTargetEntity() instanceof Player)) return; + Player player = ((Player) event.getTargetEntity()); + ItemStack stack = event.getItem(); + + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + if (wcfg.getBlacklist() != null && !wcfg.getBlacklist().check( + new ItemEquipBlacklistEvent(localPlayer, BukkitAdapter.asBlockVector(player.getLocation()), createTarget(stack)), false, true)) { + event.setCancelled(true); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlockedPotionsListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlockedPotionsListener.java new file mode 100644 index 000000000..af808213e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BlockedPotionsListener.java @@ -0,0 +1,154 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import com.sk89q.worldguard.bukkit.util.Entities; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.entity.SpectralArrow; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +/** + * Handles blocked potions. + */ +public class BlockedPotionsListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public BlockedPotionsListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler + public void onProjectile(DamageEntityEvent event) { + if (event.getOriginalEvent() instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent originalEvent = (EntityDamageByEntityEvent) event.getOriginalEvent(); + if (Entities.isPotionArrow(originalEvent.getDamager())) { // should take care of backcompat + BukkitWorldConfiguration wcfg = getWorldConfig(event.getWorld()); + PotionEffectType blockedEffect = null; + if (originalEvent.getDamager() instanceof SpectralArrow) { + if (wcfg.blockPotions.contains(PotionEffectType.GLOWING)) { + blockedEffect = PotionEffectType.GLOWING; + } + } else if (originalEvent.getDamager() instanceof Arrow) { + Arrow tippedArrow = (Arrow) originalEvent.getDamager(); + PotionEffectType baseEffect = tippedArrow.getBasePotionData().getType().getEffectType(); + if (wcfg.blockPotions.contains(baseEffect)) { + blockedEffect = baseEffect; + } else { + for (PotionEffect potionEffect : tippedArrow.getCustomEffects()) { + if (wcfg.blockPotions.contains(potionEffect.getType())) { + blockedEffect = potionEffect.getType(); + break; + } + } + } + } + if (blockedEffect != null) { + Player player = event.getCause().getFirstPlayer(); + if (player != null) { + if (getPlugin().hasPermission(player, "worldguard.override.potions")) { + return; + } + player.sendMessage(ChatColor.RED + "Sorry, arrows with " + + blockedEffect.getName() + " are presently disabled."); + } + event.setCancelled(true); + } + } + } + } + + @EventHandler + public void onItemInteract(UseItemEvent event) { + BukkitWorldConfiguration wcfg = getWorldConfig(event.getWorld()); + ItemStack item = event.getItemStack(); + + if (item.getType() != Material.POTION + && item.getType() != Material.SPLASH_POTION + && item.getType() != Material.LINGERING_POTION) { + return; + } + + if (!wcfg.blockPotions.isEmpty()) { + PotionEffectType blockedEffect = null; + + PotionMeta meta; + if (item.getItemMeta() instanceof PotionMeta) { + meta = ((PotionMeta) item.getItemMeta()); + } else { + return; // ok...? + } + + // Find the first blocked effect + PotionEffectType baseEffect = meta.getBasePotionData().getType().getEffectType(); + if (wcfg.blockPotions.contains(baseEffect)) { + blockedEffect = baseEffect; + } + + if (blockedEffect == null && meta.hasCustomEffects()) { + for (PotionEffect effect : meta.getCustomEffects()) { + if (wcfg.blockPotions.contains(effect.getType())) { + blockedEffect = effect.getType(); + break; + } + } + } + + if (blockedEffect != null) { + Player player = event.getCause().getFirstPlayer(); + + if (player != null) { + if (getPlugin().hasPermission(player, "worldguard.override.potions")) { + if (wcfg.blockPotionsAlways && (item.getType() == Material.SPLASH_POTION + || item.getType() == Material.LINGERING_POTION)) { + player.sendMessage(ChatColor.RED + "Sorry, potions with " + + blockedEffect.getName() + " can't be thrown, " + + "even if you have a permission to bypass it, " + + "due to limitations (and because overly-reliable potion blocking is on)."); + event.setCancelled(true); + } + } else { + player.sendMessage(ChatColor.RED + "Sorry, potions with " + + blockedEffect.getName() + " are presently disabled."); + event.setCancelled(true); + } + } else { + event.setCancelled(true); + } + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BuildPermissionListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BuildPermissionListener.java new file mode 100644 index 000000000..2ad78b4b1 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/BuildPermissionListener.java @@ -0,0 +1,208 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +public class BuildPermissionListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public BuildPermissionListener(WorldGuardPlugin plugin) { + super(plugin); + } + + private boolean hasBuildPermission(CommandSender sender, String perm) { + return getPlugin().hasPermission(sender, "worldguard.build." + perm); + } + + private void tellErrorMessage(CommandSender sender, World world) { + String message = getWorldConfig(world).buildPermissionDenyMessage; + if (!message.isEmpty()) { + sender.sendMessage(message); + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceBlock(final PlaceBlockEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final Material material = event.getEffectiveMaterial(); + + if (!hasBuildPermission(player, "block." + material.name().toLowerCase() + ".place") + && !hasBuildPermission(player, "block.place." + material.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBreakBlock(final BreakBlockEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final Material material = event.getEffectiveMaterial(); + + if (!hasBuildPermission(player, "block." + material.name().toLowerCase() + ".remove") + && !hasBuildPermission(player, "block.remove." + material.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseBlock(final UseBlockEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final Material material = event.getEffectiveMaterial(); + + if (!hasBuildPermission(player, "block." + material.name().toLowerCase() + ".interact") + && !hasBuildPermission(player, "block.interact." + material.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onSpawnEntity(SpawnEntityEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final EntityType type = event.getEffectiveType(); + + if (!hasBuildPermission(player, "entity." + type.name().toLowerCase() + ".place") + && !hasBuildPermission(player, "entity.place." + type.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onDestroyEntity(DestroyEntityEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final EntityType type = event.getEntity().getType(); + + if (!hasBuildPermission(player, "entity." + type.name().toLowerCase() + ".remove") + && !hasBuildPermission(player, "entity.remove." + type.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseEntity(UseEntityEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final EntityType type = event.getEntity().getType(); + + if (!hasBuildPermission(player, "entity." + type.name().toLowerCase() + ".interact") + && !hasBuildPermission(player, "entity.interact." + type.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onDamageEntity(DamageEntityEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + final Player player = (Player) rootCause; + final EntityType type = event.getEntity().getType(); + + if (!hasBuildPermission(player, "entity." + type.name().toLowerCase() + ".damage") + && !hasBuildPermission(player, "entity.damage." + type.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseItem(UseItemEvent event) { + if (!getWorldConfig(event.getWorld()).buildPermissions) return; + + Object rootCause = event.getCause().getRootCause(); + + if (rootCause instanceof Player) { + Player player = (Player) rootCause; + Material material = event.getItemStack().getType(); + + if (material.isBlock()) { + return; + } + + if (!hasBuildPermission(player, "item." + material.name().toLowerCase() + ".use") + && !hasBuildPermission(player, "item.use." + material.name().toLowerCase())) { + tellErrorMessage(player, event.getWorld()); + event.setCancelled(true); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/ChestProtectionListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/ChestProtectionListener.java new file mode 100644 index 000000000..d4d201032 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/ChestProtectionListener.java @@ -0,0 +1,192 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.SignChangeEvent; + +/** + * Handle events that need to be processed by the chest protection. + */ +public class ChestProtectionListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public ChestProtectionListener(WorldGuardPlugin plugin) { + super(plugin); + } + + private void sendMessage(DelegateEvent event, Player player, String message) { + if (!event.isSilent()) { + player.sendMessage(message); + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceBlock(final PlaceBlockEvent event) { + final Player player = event.getCause().getFirstPlayer(); + + if (player != null) { + final BukkitWorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Early guard + if (!wcfg.signChestProtection) { + return; + } + + event.filter(target -> { + if (wcfg.getChestProtection().isChest(BukkitAdapter.asBlockType(event.getEffectiveMaterial())) && wcfg.isChestProtected(BukkitAdapter.adapt(target.getBlock().getLocation()), + WorldGuardPlugin.inst().wrapPlayer(player))) { + sendMessage(event, player, ChatColor.DARK_RED + "This spot is for a chest that you don't have permission for."); + return false; + } + + return true; + }, true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBreakBlock(final BreakBlockEvent event) { + final Player player = event.getCause().getFirstPlayer(); + + final BukkitWorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Early guard + if (!wcfg.signChestProtection) { + return; + } + + if (player != null) { + final LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + event.filter(target -> { + if (wcfg.isChestProtected(BukkitAdapter.adapt(target.getBlock().getLocation()), localPlayer)) { + sendMessage(event, player, ChatColor.DARK_RED + "This chest is protected."); + return false; + } + + return true; + }, true); + } else { + event.filter(target -> !wcfg.isChestProtected(BukkitAdapter.adapt(target.getBlock().getLocation()))); + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseBlock(final UseBlockEvent event) { + final Player player = event.getCause().getFirstPlayer(); + + final BukkitWorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + // Early guard + if (!wcfg.signChestProtection) { + return; + } + + if (player != null) { + final LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + event.filter(target -> { + if (wcfg.isChestProtected(BukkitAdapter.adapt(target.getBlock().getLocation()), localPlayer)) { + sendMessage(event, player, ChatColor.DARK_RED + "This chest is protected."); + return false; + } + + return true; + }, true); + } else { + event.filter(target -> !wcfg.isChestProtected(BukkitAdapter.adapt(target.getBlock().getLocation()))); + } + } + + @EventHandler(ignoreCancelled = true) + public void onSignChange(SignChangeEvent event) { + Player player = event.getPlayer(); + final BukkitWorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + if (wcfg.signChestProtection) { + if ("[Lock]".equalsIgnoreCase(event.getLine(0))) { + if (wcfg.isChestProtectedPlacement(BukkitAdapter.adapt(event.getBlock().getLocation()), WorldGuardPlugin.inst().wrapPlayer(player))) { + player.sendMessage(ChatColor.DARK_RED + "You do not own the adjacent chest."); + event.getBlock().breakNaturally(); + event.setCancelled(true); + return; + } + + if (!Tag.STANDING_SIGNS.isTagged(event.getBlock().getType())) { + player.sendMessage(ChatColor.RED + + "The [Lock] sign must be a sign post, not a wall sign."); + + event.getBlock().breakNaturally(); + event.setCancelled(true); + return; + } + + if (!player.getName().equalsIgnoreCase(event.getLine(1))) { + player.sendMessage(ChatColor.RED + + "The first owner line must be your name."); + + event.getBlock().breakNaturally(); + event.setCancelled(true); + return; + } + + Material below = event.getBlock().getRelative(0, -1, 0).getType(); + + if (below == Material.TNT || below == Material.SAND + || below == Material.GRAVEL || Tag.STANDING_SIGNS.isTagged(below)) { + player.sendMessage(ChatColor.RED + + "That is not a safe block that you're putting this sign on."); + + event.getBlock().breakNaturally(); + event.setCancelled(true); + return; + } + + event.setLine(0, "[Lock]"); + player.sendMessage(ChatColor.YELLOW + + "A chest or double chest above is now protected."); + } + } else if (!wcfg.disableSignChestProtectionCheck) { + if ("[Lock]".equalsIgnoreCase(event.getLine(0))) { + player.sendMessage(ChatColor.RED + + "WorldGuard's sign chest protection is disabled."); + + event.getBlock().breakNaturally(); + event.setCancelled(true); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/DebuggingListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/DebuggingListener.java new file mode 100644 index 000000000..359f927d6 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/DebuggingListener.java @@ -0,0 +1,229 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerInteractEvent; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class DebuggingListener extends AbstractListener { + + private final Logger logger; + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + * @param logger the logger + */ + public DebuggingListener(WorldGuardPlugin plugin, Logger logger) { + super(plugin); + checkNotNull(logger); + this.logger = logger; + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlaceBlock(PlaceBlockEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("PLACE"); + builder.append(" "); + builder.append(event.getEffectiveMaterial()); + builder.append(" "); + builder.append("@").append(toBlockString(event.getBlocks())); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onBreakBlock(BreakBlockEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("DIG"); + builder.append(" "); + builder.append(event.getEffectiveMaterial()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getBlocks())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onUseBlock(UseBlockEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("INTERACT"); + builder.append(" "); + builder.append(event.getEffectiveMaterial()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getBlocks())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getOriginalEvent() instanceof PlayerInteractEvent) { + builder.append(".").append(((PlayerInteractEvent) event.getOriginalEvent()).getAction()); + } + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onSpawnEntity(SpawnEntityEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("SPAWN"); + builder.append(" "); + builder.append(event.getEffectiveType()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getTarget())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onDestroyEntity(DestroyEntityEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("DESTROY"); + builder.append(" "); + builder.append(event.getEntity().getType()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getTarget())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onUseEntity(UseEntityEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("INTERACT"); + builder.append(" "); + builder.append(event.getEntity().getType()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getTarget())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onDamageEntity(DamageEntityEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("DAMAGE"); + builder.append(" "); + builder.append(event.getEntity().getType()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(toBlockString(event.getTarget())); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onUseItem(UseItemEvent event) { + StringBuilder builder = new StringBuilder(); + builder.append("USE"); + builder.append(" "); + builder.append(event.getItemStack().getType()); + builder.append(" "); + builder.append("[").append(event.getCause()).append("]"); + builder.append(" "); + builder.append("@").append(event.getWorld().getName()); + builder.append(" "); + builder.append(": ").append(getEventName(event.getOriginalEvent())); + if (event.getResult() != Result.DEFAULT) { + builder.append(" [").append(event.getResult()).append("]"); + } + logger.info(builder.toString()); + } + + private static String toBlockString(Location location) { + return location.getBlockX() + "," + location.getBlockY() + "," + location.getBlockZ(); + } + + private static String toBlockString(List blocks) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (Block block : blocks) { + if (!first) { + builder.append("|"); + } + builder.append(block.getX()).append(",").append(block.getY()).append(",").append(block.getZ()); + first = false; + } + return builder.toString(); + } + + private String getEventName(@Nullable Event event) { + return event != null ? event.getEventName() : "?"; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java new file mode 100644 index 000000000..edb4eac58 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/EventAbstractionListener.java @@ -0,0 +1,1267 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import static com.sk89q.worldguard.bukkit.cause.Cause.create; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import com.sk89q.worldguard.bukkit.event.inventory.UseItemEvent; +import com.sk89q.worldguard.bukkit.listener.debounce.BlockPistonExtendKey; +import com.sk89q.worldguard.bukkit.listener.debounce.BlockPistonRetractKey; +import com.sk89q.worldguard.bukkit.listener.debounce.EventDebounce; +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.AbstractEventDebounce.Entry; +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.BlockEntityEventDebounce; +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.EntityEntityEventDebounce; +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.InventoryMoveItemEventDebounce; +import com.sk89q.worldguard.bukkit.util.Blocks; +import com.sk89q.worldguard.bukkit.util.Entities; +import com.sk89q.worldguard.bukkit.util.Events; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.flags.Flags; +import org.bukkit.Bukkit; +import org.bukkit.Effect; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.Chest; +import org.bukkit.block.DoubleChest; +import org.bukkit.block.Hopper; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.block.data.type.Dispenser; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Firework; +import org.bukkit.entity.Item; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Painting; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.entity.minecart.HopperMinecart; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockDamageEvent; +import org.bukkit.event.block.BlockDispenseEvent; +import org.bukkit.event.block.BlockExpEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFertilizeEvent; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockMultiPlaceEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.CauldronLevelChangeEvent; +import org.bukkit.event.block.EntityBlockFormEvent; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.entity.AreaEffectCloudApplyEvent; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityCombustByBlockEvent; +import org.bukkit.event.entity.EntityCombustByEntityEvent; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityTameEvent; +import org.bukkit.event.entity.EntityUnleashEvent; +import org.bukkit.event.entity.ExpBottleEvent; +import org.bukkit.event.entity.LingeringPotionSplashEvent; +import org.bukkit.event.entity.PotionSplashEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.hanging.HangingPlaceEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryOpenEvent; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.bukkit.event.player.PlayerBucketEmptyEvent; +import org.bukkit.event.player.PlayerBucketFillEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.event.player.PlayerPickupItemEvent; +import org.bukkit.event.player.PlayerShearEntityEvent; +import org.bukkit.event.player.PlayerTakeLecternBookEvent; +import org.bukkit.event.player.PlayerUnleashEntityEvent; +import org.bukkit.event.vehicle.VehicleDamageEvent; +import org.bukkit.event.vehicle.VehicleDestroyEvent; +import org.bukkit.event.world.StructureGrowEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.util.Vector; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class EventAbstractionListener extends AbstractListener { + + private final BlockEntityEventDebounce interactDebounce = new BlockEntityEventDebounce(10000); + private final EntityEntityEventDebounce pickupDebounce = new EntityEntityEventDebounce(10000); + private final BlockEntityEventDebounce entityBreakBlockDebounce = new BlockEntityEventDebounce(10000); + private final InventoryMoveItemEventDebounce moveItemDebounce = new InventoryMoveItemEventDebounce(30000); + private final EventDebounce pistonRetractDebounce = EventDebounce.create(5000); + private final EventDebounce pistonExtendDebounce = EventDebounce.create(5000); + + private static final boolean HAS_SNAPSHOT_INVHOLDER; + static { + boolean temp; + try { + Inventory.class.getMethod("getHolder", boolean.class); + temp = true; + } catch (NoSuchMethodException e) { + temp = false; + } + HAS_SNAPSHOT_INVHOLDER = temp; + } + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public EventAbstractionListener(WorldGuardPlugin plugin) { + super(plugin); + } + + //------------------------------------------------------------------------- + // Block break / place + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + Events.fireToCancel(event, new BreakBlockEvent(event, create(event.getPlayer()), event.getBlock())); + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), event.getBlock().getLocation().add(0.5, 1, 0.5)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockMultiPlace(BlockMultiPlaceEvent event) { + List placed = event.getReplacedBlockStates().stream().map(BlockState::getBlock).collect(Collectors.toList()); + int origAmt = placed.size(); + PlaceBlockEvent delegateEvent = new PlaceBlockEvent(event, create(event.getPlayer()), event.getBlock().getWorld(), + placed, event.getBlockPlaced().getType()); + Events.fireToCancel(event, delegateEvent); + if (origAmt != placed.size()) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + if (event instanceof BlockMultiPlaceEvent) return; + BlockState previousState = event.getBlockReplacedState(); + + // Some blocks, like tall grass and fire, get replaced + if (previousState.getType() != Material.AIR && previousState.getType() != event.getBlockReplacedState().getType()) { + Events.fireToCancel(event, new BreakBlockEvent(event, create(event.getPlayer()), previousState.getLocation(), previousState.getType())); + } + + if (!event.isCancelled()) { + ItemStack itemStack = new ItemStack(event.getBlockPlaced().getType(), 1); + Events.fireToCancel(event, new UseItemEvent(event, create(event.getPlayer()), event.getPlayer().getWorld(), itemStack)); + } + + if (!event.isCancelled()) { + Events.fireToCancel(event, new PlaceBlockEvent(event, create(event.getPlayer()), event.getBlock())); + } + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), event.getBlockPlaced().getLocation().add(0.5, 0.5, 0.5)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockBurn(BlockBurnEvent event) { + Block target = event.getBlock(); + + Block[] adjacent = { + target.getRelative(BlockFace.NORTH), + target.getRelative(BlockFace.SOUTH), + target.getRelative(BlockFace.WEST), + target.getRelative(BlockFace.EAST), + target.getRelative(BlockFace.UP), + target.getRelative(BlockFace.DOWN)}; + + int found = 0; + boolean allowed = false; + + for (Block source : adjacent) { + if (Materials.isFire(source.getType())) { + found++; + if (Events.fireAndTestCancel(new BreakBlockEvent(event, create(source), target))) { + source.setType(Material.AIR); + } else { + allowed = true; + } + } + } + + if (found > 0 && !allowed) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onStructureGrowEvent(StructureGrowEvent event) { + int originalCount = event.getBlocks().size(); + + Player player = event.getPlayer(); + Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, + create(player == null ? event.getLocation().getBlock() : player), + event.getLocation().getWorld(), event.getBlocks())); + + if (!event.isCancelled() && event.getBlocks().size() != originalCount) { + event.getLocation().getBlock().setType(Material.AIR); + } + } + + private void handleFallingBlock(EntityChangeBlockEvent event, boolean dropItem) { + Entity entity = event.getEntity(); + Block block = event.getBlock(); + + if (entity instanceof FallingBlock) { + try { + if (dropItem) { + FallingBlock fallingBlock = (FallingBlock) entity; + if (!fallingBlock.getDropItem()) return; + final Material material = fallingBlock.getBlockData().getMaterial(); + if (!material.isItem()) return; + ItemStack itemStack = new ItemStack(material, 1); + Item item = block.getWorld().dropItem(fallingBlock.getLocation(), itemStack); + item.setVelocity(new Vector()); + if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(block, entity), item))) { + item.remove(); + } + } + } finally { + Cause.untrackParentCause(entity); + } + } + } + + private void setDelegateEventMaterialOptions(DelegateEvent event, Material fromType, Material toType) { + if (fromType == Material.FARMLAND && toType == Material.DIRT) { + event.setSilent(true); + event.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); + } else if (Tag.REDSTONE_ORES.isTagged(fromType)) { + event.setSilent(true); + } else if (fromType == Material.BIG_DRIPLEAF && toType == Material.BIG_DRIPLEAF) { + event.setSilent(true); + event.getRelevantFlags().add(Flags.USE_DRIPLEAF); + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityChangeBlock(EntityChangeBlockEvent event) { + Block block = event.getBlock(); + Entity entity = event.getEntity(); + Material toType = event.getTo(); + Material fromType = block.getType(); + Cause cause = create(entity); + + // Fire two events: one as BREAK and one as PLACE + if (toType != Material.AIR && fromType != Material.AIR) { + BreakBlockEvent breakDelagate = new BreakBlockEvent(event, cause, block); + setDelegateEventMaterialOptions(breakDelagate, fromType, toType); + boolean denied; + if (!(denied = Events.fireToCancel(event, breakDelagate))) { + PlaceBlockEvent placeDelegate = new PlaceBlockEvent(event, cause, block.getLocation(), toType); + setDelegateEventMaterialOptions(placeDelegate, fromType, toType); + denied = Events.fireToCancel(event, placeDelegate); + } + if (denied && entity instanceof Player) { + playDenyEffect((Player) entity, block.getLocation()); + } + + handleFallingBlock(event, denied); + } else if (toType == Material.AIR) { + // Track the source so later we can create a proper chain of causes + if (entity instanceof FallingBlock) { + Cause.trackParentCause(entity, block); + + // Switch around the event + Events.fireToCancel(event, new SpawnEntityEvent(event, create(block), entity)); + } else { + entityBreakBlockDebounce.debounce( + block, event.getEntity(), event, new BreakBlockEvent(event, cause, block)); + } + } else { // toType != Material.AIR && fromType == Material.AIR + boolean denied = Events.fireToCancel(event, new PlaceBlockEvent(event, cause, block.getLocation(), toType)); + handleFallingBlock(event, denied); + } + + } + + @EventHandler(ignoreCancelled = true) + public void onEntityExplode(EntityExplodeEvent event) { + Entity entity = event.getEntity(); + Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, create(entity), event.getLocation().getWorld(), event.blockList(), Material.AIR)); + if (entity instanceof Creeper) { + Cause.untrackParentCause(entity); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockPistonRetract(BlockPistonRetractEvent event) { + if (event.isSticky()) { + EventDebounce.Entry entry = pistonRetractDebounce.getIfNotPresent(new BlockPistonRetractKey(event), event); + if (entry != null) { + Block piston = event.getBlock(); + Cause cause = create(piston); + + BlockFace direction = event.getDirection(); + + ArrayList blocks = new ArrayList<>(event.getBlocks()); + int originalSize = blocks.size(); + Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.AIR)); + if (originalSize != blocks.size()) { + event.setCancelled(true); + return; + } + for (Block b : blocks) { + Location loc = b.getRelative(direction).getLocation(); + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, loc, b.getType())); + } + + entry.setCancelled(event.isCancelled()); + + if (event.isCancelled()) { + playDenyEffect(piston.getLocation().add(0.5, 1, 0.5)); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockPistonExtend(BlockPistonExtendEvent event) { + EventDebounce.Entry entry = pistonExtendDebounce.getIfNotPresent(new BlockPistonExtendKey(event), event); + if (entry != null) { + Cause cause = create(event.getBlock()); + List blocks = new ArrayList<>(event.getBlocks()); + int originalLength = blocks.size(); + Events.fireBulkEventToCancel(event, new BreakBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.AIR)); + if (originalLength != blocks.size()) { + event.setCancelled(true); + return; + } + BlockFace dir = event.getDirection(); + for (int i = 0; i < blocks.size(); i++) { + Block existing = blocks.get(i); + if (existing.getPistonMoveReaction() == PistonMoveReaction.MOVE + || existing.getPistonMoveReaction() == PistonMoveReaction.PUSH_ONLY + || existing.getType() == Material.PISTON || existing.getType() == Material.STICKY_PISTON) { + blocks.set(i, existing.getRelative(dir)); + } + } + Events.fireBulkEventToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getWorld(), blocks, Material.STONE)); + if (blocks.size() != originalLength) { + event.setCancelled(true); + } + entry.setCancelled(event.isCancelled()); + + if (event.isCancelled()) { + playDenyEffect(event.getBlock().getLocation().add(0.5, 1, 0.5)); + } + } + } + + //------------------------------------------------------------------------- + // Block external interaction + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onBlockDamage(BlockDamageEvent event) { + Block target = event.getBlock(); + + // Previously, and perhaps still, the only way to catch cake eating + // events was through here + if (target.getType() == Material.CAKE) { + Events.fireToCancel(event, new UseBlockEvent(event, create(event.getPlayer()), target)); + } + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); + @Nullable ItemStack item = event.getItem(); + Block clicked = event.getClickedBlock(); + Block placed; + boolean modifiesWorld; + Cause cause = create(player); + + switch (event.getAction()) { + case PHYSICAL: + if (event.useInteractedBlock() != Result.DENY) { + if (clicked.getType() == Material.FARMLAND || clicked.getType() == Material.TURTLE_EGG) { + BreakBlockEvent breakDelagate = new BreakBlockEvent(event, cause, clicked); + breakDelagate.setSilent(true); + breakDelagate.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); + boolean denied; + if (!(denied = Events.fireToCancel(event, breakDelagate))) { + PlaceBlockEvent placeDelegate = new PlaceBlockEvent(event, cause, clicked.getLocation(), + clicked.getType() == Material.FARMLAND ? Material.DIRT : clicked.getType()); + placeDelegate.setSilent(true); + placeDelegate.getRelevantFlags().add(Flags.TRAMPLE_BLOCKS); + denied = Events.fireToCancel(event, placeDelegate); + } + if (denied) { + playDenyEffect(player, clicked.getLocation()); + } + return; + } + DelegateEvent firedEvent = new UseBlockEvent(event, cause, clicked).setAllowed(hasInteractBypass(clicked)); + if (Tag.REDSTONE_ORES.isTagged(clicked.getType())) { + firedEvent.setSilent(true); + } + if (clicked.getType() == Material.BIG_DRIPLEAF) { + firedEvent.getRelevantFlags().add(Flags.USE_DRIPLEAF); + firedEvent.setSilent(true); + } + interactDebounce.debounce(clicked, event.getPlayer(), event, firedEvent); + if (event.useInteractedBlock() == Result.DENY) { + playDenyEffect(player, clicked.getLocation().add(0, 1, 0)); + } + } + break; + + case RIGHT_CLICK_BLOCK: + if (event.useInteractedBlock() != Result.DENY) { + placed = clicked.getRelative(event.getBlockFace()); + + // Re-used for dispensers + handleBlockRightClick(event, create(event.getPlayer()), item, clicked, placed); + } + + case LEFT_CLICK_BLOCK: + if (event.useInteractedBlock() != Result.DENY) { + placed = clicked.getRelative(event.getBlockFace()); + + // Only fire events for blocks that are modified when right clicked + final boolean hasItemInteraction = item != null && isItemAppliedToBlock(item, clicked) + && event.getAction() == Action.RIGHT_CLICK_BLOCK; + modifiesWorld = hasItemInteraction + || isBlockModifiedOnClick(clicked, event.getAction() == Action.RIGHT_CLICK_BLOCK); + + if (Events.fireAndTestCancel(new UseBlockEvent(event, cause, clicked).setAllowed(!modifiesWorld))) { + event.setUseInteractedBlock(Result.DENY); + } + + // Handle connected blocks (i.e. beds, chests) + for (Block connected : Blocks.getConnected(clicked)) { + if (Events.fireAndTestCancel(new UseBlockEvent(event, create(event.getPlayer()), connected).setAllowed(!modifiesWorld))) { + event.setUseInteractedBlock(Result.DENY); + break; + } + } + + if (hasItemInteraction) { + if (Events.fireAndTestCancel(new PlaceBlockEvent(event, cause, clicked.getLocation(), clicked.getType()))) { + event.setUseItemInHand(Result.DENY); + event.setUseInteractedBlock(Result.DENY); + } + } + + // Special handling of putting out fires + if (event.getAction() == Action.LEFT_CLICK_BLOCK && Materials.isFire(placed.getType())) { + if (Events.fireAndTestCancel(new BreakBlockEvent(event, create(event.getPlayer()), placed))) { + event.setUseInteractedBlock(Result.DENY); + break; + } + } + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), clicked.getLocation().add(0.5, 1, 0.5)); + } + } + + case LEFT_CLICK_AIR: + case RIGHT_CLICK_AIR: + if (event.useItemInHand() != Result.DENY) { + if (item != null && !item.getType().isBlock() && Events.fireAndTestCancel(new UseItemEvent(event, cause, player.getWorld(), item))) { + event.setUseItemInHand(Result.DENY); + } + } + + // Check for items that the administrator has configured to + // emit a "use block here" event where the player is + // standing, which is a hack to protect items that don't + // throw events + if (item != null && getWorldConfig(player.getWorld()).blockUseAtFeet.test(item)) { + if (Events.fireAndTestCancel(new UseBlockEvent(event, cause, player.getLocation().getBlock()))) { + event.setUseInteractedBlock(Result.DENY); + } + } + + break; + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityBlockForm(EntityBlockFormEvent event) { + entityBreakBlockDebounce.debounce(event.getBlock(), event.getEntity(), event, + new PlaceBlockEvent(event, create(event.getEntity()), + event.getBlock().getLocation(), event.getNewState().getType())); + } + + @EventHandler(ignoreCancelled = true) + public void onEntityInteract(EntityInteractEvent event) { + interactDebounce.debounce(event.getBlock(), event.getEntity(), event, + new UseBlockEvent(event, create(event.getEntity()), + event.getBlock()).setAllowed(hasInteractBypass(event.getBlock()))); + } + + @EventHandler(ignoreCancelled = true) + public void onBlockFertilize(BlockFertilizeEvent event) { + if (event.getBlocks().isEmpty()) return; + Cause cause = create(event.getPlayer(), event.getBlock()); + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, event.getBlock().getWorld(), event.getBlocks())); + } + + @EventHandler(ignoreCancelled = true) + public void onBlockIgnite(BlockIgniteEvent event) { + Block block = event.getBlock(); + Cause cause; + + // Find the cause + if (event.getPlayer() != null) { + cause = create(event.getPlayer()); + } else if (event.getIgnitingEntity() != null) { + cause = create(event.getIgnitingEntity()); + } else if (event.getIgnitingBlock() != null) { + cause = create(event.getIgnitingBlock()); + } else { + cause = Cause.unknown(); + } + + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, block.getLocation(), Material.FIRE)); + } + + @EventHandler(ignoreCancelled = true) + public void onSignChange(SignChangeEvent event) { + Events.fireToCancel(event, new UseBlockEvent(event, create(event.getPlayer()), event.getBlock())); + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), event.getBlock().getLocation().add(0.5, 0.5, 0.5)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onBedEnter(PlayerBedEnterEvent event) { + Events.fireToCancel(event, new UseBlockEvent(event, create(event.getPlayer()), event.getBed())); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerBucketEmpty(PlayerBucketEmptyEvent event) { + Player player = event.getPlayer(); + Block blockClicked = event.getBlockClicked(); + Block blockAffected; + + if (blockClicked.getBlockData() instanceof Waterlogged) { + blockAffected = blockClicked; + } else { + blockAffected = blockClicked.getRelative(event.getBlockFace()); + } + + boolean allowed = false; + + // Milk buckets can't be emptied as of writing + if (event.getBucket() == Material.MILK_BUCKET) { + allowed = true; + } + + ItemStack item = new ItemStack(event.getBucket(), 1); + Material blockMaterial = Materials.getBucketBlockMaterial(event.getBucket()); + Events.fireToCancel(event, new PlaceBlockEvent(event, create(player), blockAffected.getLocation(), blockMaterial).setAllowed(allowed)); + Events.fireToCancel(event, new UseItemEvent(event, create(player), player.getWorld(), item).setAllowed(allowed)); + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), blockAffected.getLocation().add(0.5, 0.5, 0.5)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerBucketFill(PlayerBucketFillEvent event) { + Player player = event.getPlayer(); + Block blockAffected = event.getBlockClicked().getRelative(event.getBlockFace()); + boolean allowed = false; + + // Milk buckets can't be emptied as of writing + if (event.getItemStack().getType() == Material.MILK_BUCKET) { + allowed = true; + } + + ItemStack item = new ItemStack(event.getBucket(), 1); + Events.fireToCancel(event, new BreakBlockEvent(event, create(player), blockAffected).setAllowed(allowed)); + Events.fireToCancel(event, new UseItemEvent(event, create(player), player.getWorld(), item).setAllowed(allowed)); + + if (event.isCancelled()) { + playDenyEffect(event.getPlayer(), blockAffected.getLocation().add(0.5, 0.5, 0.5)); + } + } + + // TODO: Handle EntityPortalEnterEvent + + //------------------------------------------------------------------------- + // Block self-interaction + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onBlockFromTo(BlockFromToEvent event) { + WorldConfiguration config = getWorldConfig(event.getBlock().getWorld()); + + // This only applies to regions but nothing else cares about high + // frequency events at the moment + if (!config.useRegions || (!config.highFreqFlags && !config.checkLiquidFlow)) { + return; + } + + Block from = event.getBlock(); + Block to = event.getToBlock(); + Material fromType = from.getType(); + Material toType = to.getType(); + + // Liquids pass this event when flowing to solid blocks + if (Materials.isLiquid(fromType) && toType.isSolid()) { + return; + } + + // This significantly reduces the number of events without having + // too much effect. Unfortunately it appears that even if this + // check didn't exist, you can raise the level of some liquid + // flow and the from/to data may not be correct. + if ((Materials.isWater(fromType) && Materials.isWater(toType)) || (Materials.isLava(fromType) && Materials.isLava(toType))) { + return; + } + + Cause cause = create(from); + + // Disable since it's probably not needed + /*if (from.getType() != Material.AIR) { + Events.fireToCancel(event, new BreakBlockEvent(event, cause, to)); + }*/ + + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, to.getLocation(), from.getType())); + } + + //------------------------------------------------------------------------- + // Entity break / place + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onCreatureSpawn(CreatureSpawnEvent event) { + switch (event.getSpawnReason()) { + case DISPENSE_EGG: + case EGG: + case SPAWNER_EGG: + if (getWorldConfig(event.getEntity().getWorld()).strictEntitySpawn) { + Events.fireToCancel(event, new SpawnEntityEvent(event, Cause.unknown(), event.getEntity())); + } + break; + default: + } + } + + @EventHandler(ignoreCancelled = true) + public void onHangingPlace(HangingPlaceEvent event) { + Events.fireToCancel(event, new SpawnEntityEvent(event, create(event.getPlayer()), event.getEntity())); + + if (event.isCancelled()) { + Block effectBlock = event.getBlock().getRelative(event.getBlockFace()); + playDenyEffect(event.getPlayer(), effectBlock.getLocation().add(0.5, 0.5, 0.5)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onHangingBreak(HangingBreakEvent event) { + if (event instanceof HangingBreakByEntityEvent) { + Entity remover = ((HangingBreakByEntityEvent) event).getRemover(); + Events.fireToCancel(event, new DestroyEntityEvent(event, create(remover), event.getEntity())); + + if (event.isCancelled() && remover instanceof Player) { + playDenyEffect((Player) remover, event.getEntity().getLocation()); + } + } else if (event.getCause() == HangingBreakEvent.RemoveCause.EXPLOSION){ + DestroyEntityEvent destroyEntityEvent = new DestroyEntityEvent(event, Cause.unknown(), event.getEntity()); + destroyEntityEvent.getRelevantFlags().add(Flags.OTHER_EXPLOSION); + if (event.getEntity() instanceof ItemFrame) { + destroyEntityEvent.getRelevantFlags().add(Flags.ENTITY_ITEM_FRAME_DESTROY); + } else if (event.getEntity() instanceof Painting) { + destroyEntityEvent.getRelevantFlags().add(Flags.ENTITY_PAINTING_DESTROY); + } + Events.fireToCancel(event, destroyEntityEvent); + } + } + + @EventHandler(ignoreCancelled = true) + public void onVehicleDestroy(VehicleDestroyEvent event) { + Events.fireToCancel(event, new DestroyEntityEvent(event, create(event.getAttacker()), event.getVehicle())); + } + + @EventHandler(ignoreCancelled = true) + public void onBlockExp(BlockExpEvent event) { + if (event.getExpToDrop() > 0) { // Event is raised even where no XP is being dropped + if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getBlock()), event.getBlock().getLocation(), EntityType.EXPERIENCE_ORB))) { + event.setExpToDrop(0); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerFish(PlayerFishEvent event) { + if (event.getState() == PlayerFishEvent.State.FISHING) { + if (Events.fireAndTestCancel(new UseItemEvent(event, create(event.getPlayer(), event.getHook()), + event.getPlayer().getWorld(), event.getPlayer().getInventory().getItemInMainHand()))) { + event.setCancelled(true); + } + } else if (event.getState() == PlayerFishEvent.State.CAUGHT_FISH) { + if (event.getExpToDrop() > 0 && Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getPlayer(), event.getHook()), event.getHook().getLocation(), EntityType.EXPERIENCE_ORB))) { + event.setExpToDrop(0); + } + } else if (event.getState() == PlayerFishEvent.State.CAUGHT_ENTITY) { + Entity caught = event.getCaught(); + if (caught == null) return; + if (caught instanceof Item) { + Events.fireToCancel(event, new DestroyEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); + } else if (Entities.isConsideredBuildingIfUsed(caught)) { + Events.fireToCancel(event, new UseEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); + } else if (Entities.isNonHostile(caught) || caught instanceof Player) { + Events.fireToCancel(event, new DamageEntityEvent(event, create(event.getPlayer(), event.getHook()), caught)); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onExpBottle(ExpBottleEvent event) { + if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getEntity()), event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB))) { + event.setExperience(0); + + // Give the player back his or her XP bottle + ProjectileSource shooter = event.getEntity().getShooter(); + if (shooter instanceof Player) { + Player player = (Player) shooter; + if (player.getGameMode() != GameMode.CREATIVE) { + player.getInventory().addItem(new ItemStack(Material.EXPERIENCE_BOTTLE, 1)); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityDeath(EntityDeathEvent event) { + if (event.getDroppedExp() > 0) { + if (Events.fireAndTestCancel(new SpawnEntityEvent(event, create(event.getEntity()), event.getEntity().getLocation(), EntityType.EXPERIENCE_ORB))) { + event.setDroppedExp(0); + } + } + } + + //------------------------------------------------------------------------- + // Entity external interaction + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + Player player = event.getPlayer(); + World world = player.getWorld(); + ItemStack item = event.getHand() == EquipmentSlot.OFF_HAND + ? player.getInventory().getItemInOffHand() : player.getInventory().getItemInMainHand(); + Entity entity = event.getRightClicked(); + + if (Events.fireToCancel(event, new UseItemEvent(event, create(player), world, item))) { + return; + } + final UseEntityEvent useEntityEvent = new UseEntityEvent(event, create(player), entity); + Material matchingItem = Materials.getRelatedMaterial(entity.getType()); + if (matchingItem != null && hasInteractBypass(world, matchingItem)) { + useEntityEvent.setAllowed(true); + } + if (!Events.fireToCancel(event, useEntityEvent)) { + // so this is a hack but CreeperIgniteEvent doesn't actually tell us who, so we need to do it here + if (item.getType() == Material.FLINT_AND_STEEL && entity.getType() == EntityType.CREEPER) { + Cause.trackParentCause(entity, player); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + if (event instanceof EntityDamageByBlockEvent) { + @Nullable Block attacker = ((EntityDamageByBlockEvent) event).getDamager(); + + // The attacker should NOT be null, but sometimes it is + // See WORLDGUARD-3350 + if (attacker != null) { + Events.fireToCancel(event, new DamageEntityEvent(event, create(attacker), event.getEntity())); + } + + } else if (event instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent entityEvent = (EntityDamageByEntityEvent) event; + Entity damager = entityEvent.getDamager(); + final DamageEntityEvent eventToFire = new DamageEntityEvent(event, create(damager), event.getEntity()); + if (damager instanceof Firework) { + eventToFire.getRelevantFlags().add(Flags.FIREWORK_DAMAGE); + } else if (damager instanceof Creeper) { + eventToFire.getRelevantFlags().add(Flags.CREEPER_EXPLOSION); + } + if (Events.fireToCancel(event, eventToFire)) { + if (damager instanceof Tameable && damager instanceof Mob) { + ((Mob) damager).setTarget(null); + } + } + + // Item use event with the item in hand + // Older blacklist handler code used this, although it suffers from + // race problems + if (damager instanceof Player) { + // this event doesn't tell us which hand the weapon was in + ItemStack item = ((Player) damager).getInventory().getItemInMainHand(); + + if (item.getType() != Material.AIR) { + Events.fireToCancel(event, new UseItemEvent(event, create(damager), event.getEntity().getWorld(), item)); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityCombust(EntityCombustEvent event) { + if (event instanceof EntityCombustByBlockEvent) { + // at the time of writing, spigot is throwing null for the event's combuster. this causes lots of issues downstream. + // whenever (i mean if ever) it is fixed, use getCombuster again instead of the current block + Events.fireToCancel(event, new DamageEntityEvent(event, create(event.getEntity().getLocation().getBlock()), event.getEntity())); + } else if (event instanceof EntityCombustByEntityEvent) { + Events.fireToCancel(event, new DamageEntityEvent(event, create(((EntityCombustByEntityEvent) event).getCombuster()), event.getEntity())); + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityUnleash(EntityUnleashEvent event) { + if (event instanceof PlayerUnleashEntityEvent) { + PlayerUnleashEntityEvent playerEvent = (PlayerUnleashEntityEvent) event; + Events.fireToCancel(playerEvent, new UseEntityEvent(playerEvent, create(playerEvent.getPlayer()), event.getEntity())); + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityTame(EntityTameEvent event) { + Events.fireToCancel(event, new UseEntityEvent(event, create(event.getOwner()), event.getEntity())); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerShearEntity(PlayerShearEntityEvent event) { + Events.fireToCancel(event, new UseEntityEvent(event, create(event.getPlayer()), event.getEntity())); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerPickupItem(PlayerPickupItemEvent event) { + Item item = event.getItem(); + pickupDebounce.debounce(event.getPlayer(), item, event, new DestroyEntityEvent(event, create(event.getPlayer()), event.getItem())); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerDropItem(PlayerDropItemEvent event) { + Events.fireToCancel(event, new SpawnEntityEvent(event, create(event.getPlayer()), event.getItemDrop())); + } + + @EventHandler(ignoreCancelled = true) + public void onVehicleDamage(VehicleDamageEvent event) { + Entity attacker = event.getAttacker(); + Events.fireToCancel(event, new DamageEntityEvent(event, create(attacker), event.getVehicle())); + } + + //------------------------------------------------------------------------- + // Composite events + //------------------------------------------------------------------------- + + @EventHandler(ignoreCancelled = true) + public void onPlayerItemConsume(PlayerItemConsumeEvent event) { + Events.fireToCancel(event, new UseItemEvent(event, create(event.getPlayer()), event.getPlayer().getWorld(), event.getItem())); + } + + @EventHandler(ignoreCancelled = true) + public void onInventoryOpen(InventoryOpenEvent event) { + InventoryHolder holder = event.getInventory().getHolder(); + if (holder instanceof Entity && holder == event.getPlayer()) return; + + handleInventoryHolderUse(event, create(event.getPlayer()), holder); + } + + @EventHandler(ignoreCancelled = true) + public void onInventoryMoveItem(InventoryMoveItemEvent event) { + InventoryHolder causeHolder; + if (HAS_SNAPSHOT_INVHOLDER) { + causeHolder = event.getInitiator().getHolder(false); + } else { + causeHolder = event.getInitiator().getHolder(); + } + + WorldConfiguration wcfg = null; + if (causeHolder instanceof Hopper + && (wcfg = getWorldConfig((((Hopper) causeHolder).getWorld()))).ignoreHopperMoveEvents) { + return; + } else if (causeHolder instanceof HopperMinecart + && (wcfg = getWorldConfig((((HopperMinecart) causeHolder).getWorld()))).ignoreHopperMoveEvents) { + return; + } + + Entry entry; + + if ((entry = moveItemDebounce.tryDebounce(event)) != null) { + InventoryHolder sourceHolder; + InventoryHolder targetHolder; + /*if (HAS_SNAPSHOT_INVHOLDER) { + sourceHolder = event.getSource().getHolder(false); + targetHolder = event.getDestination().getHolder(false); + } else {*/ + sourceHolder = event.getSource().getHolder(); + targetHolder = event.getDestination().getHolder(); + //} + + Cause cause; + + if (causeHolder instanceof Entity) { + cause = create(causeHolder); + } else if (causeHolder instanceof BlockState) { + cause = create(((BlockState) causeHolder).getBlock()); + } else { + cause = Cause.unknown(); + } + + if (causeHolder != null && !causeHolder.equals(sourceHolder)) { + handleInventoryHolderUse(event, cause, sourceHolder); + } + + handleInventoryHolderUse(event, cause, targetHolder); + + if (event.isCancelled() && causeHolder instanceof Hopper && wcfg.breakDeniedHoppers) { + Bukkit.getScheduler().scheduleSyncDelayedTask(getPlugin(), + () -> ((Hopper) causeHolder).getBlock().breakNaturally()); + } else { + entry.setCancelled(event.isCancelled()); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPotionSplash(PotionSplashEvent event) { + Entity entity = event.getEntity(); + ThrownPotion potion = event.getPotion(); + World world = entity.getWorld(); + Cause cause = create(potion); + + // Fire item interaction event + Events.fireToCancel(event, new UseItemEvent(event, cause, world, potion.getItem())); + + // Fire entity interaction event + if (!event.isCancelled()) { + int blocked = 0; + int affectedSize = event.getAffectedEntities().size(); + boolean hasDamageEffect = Materials.hasDamageEffect(potion.getEffects()); + + for (LivingEntity affected : event.getAffectedEntities()) { + DelegateEvent delegate = hasDamageEffect + ? new DamageEntityEvent(event, cause, affected) : + new UseEntityEvent(event, cause, affected); + + // Consider the potion splash flag + delegate.getRelevantFlags().add(Flags.POTION_SPLASH); + + if (Events.fireAndTestCancel(delegate)) { + event.setIntensity(affected, 0); + blocked++; + } + } + + if (blocked == affectedSize) { // server does weird things with this if the event is modified, so use cached number + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockDispense(BlockDispenseEvent event) { + Block dispenserBlock = event.getBlock(); + + // Simulate right click event as players have it + if (dispenserBlock.getType() == Material.DISPENSER) { + Cause cause = create(event.getBlock()); + ItemStack item = event.getItem(); + if (Events.fireToCancel(event, new UseItemEvent(event, cause, dispenserBlock.getWorld(), item))) { + return; + } + + BlockData blockData = dispenserBlock.getBlockData(); + Dispenser dispenser = (Dispenser) blockData; // if this ClassCastExceptions it's a bukkit bug + Block placed = dispenserBlock.getRelative(dispenser.getFacing()); + Block clicked = placed.getRelative(dispenser.getFacing()); + handleBlockRightClick(event, cause, item, clicked, placed); + + // handle special dispenser behavior + if (Materials.isShulkerBox(item.getType())) { + if (Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), item.getType()))) { + playDenyEffect(placed.getLocation()); + } + } else if (isItemAppliedToBlock(item, placed)) { + if (Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), placed.getType()))) { + playDenyEffect(placed.getLocation()); + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onLingeringSplash(LingeringPotionSplashEvent event) { + AreaEffectCloud aec = event.getAreaEffectCloud(); + ThrownPotion potion = event.getEntity(); + World world = potion.getWorld(); + Cause cause = create(event.getEntity()); + + // Fire item interaction event + Events.fireToCancel(event, new UseItemEvent(event, cause, world, potion.getItem())); + + // Fire entity spawn event + if (!event.isCancelled()) { + // radius unfortunately doesn't go through with this, so only a single location is tested + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, aec.getLocation().add(0.5, 0, 0.5), EntityType.AREA_EFFECT_CLOUD)); + } + } + + @EventHandler(ignoreCancelled = true) + public void onLingeringApply(AreaEffectCloudApplyEvent event) { + AreaEffectCloud entity = event.getEntity(); + List effects = new ArrayList<>(); + PotionEffectType baseEffectType = entity.getBasePotionData().getType().getEffectType(); + if (baseEffectType != null) { + effects.add(new PotionEffect(baseEffectType, 0, 0)); + } + if (entity.hasCustomEffects()) { + effects.addAll(entity.getCustomEffects()); + } + if (!Materials.hasDamageEffect(effects)) { + return; + } + Cause cause = create(event.getEntity()); + event.getAffectedEntities() + .removeIf(victim -> Events.fireAndTestCancel(new DamageEntityEvent(event, cause, victim))); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent event){ + onPlayerInteractEntity(event); + } + + @EventHandler(ignoreCancelled = true) + public void onBlockExplode(BlockExplodeEvent event) { + final BreakBlockEvent eventToFire = new BreakBlockEvent(event, create(event.getBlock()), + event.getBlock().getLocation().getWorld(), event.blockList(), Material.AIR); + eventToFire.getRelevantFlags().add(Flags.OTHER_EXPLOSION); + Events.fireBulkEventToCancel(event, eventToFire); + } + + @EventHandler(ignoreCancelled = true) + public void onTakeLecternBook(PlayerTakeLecternBookEvent event) { + final UseBlockEvent useEvent = new UseBlockEvent(event, create(event.getPlayer()), event.getLectern().getBlock()); + Events.fireToCancel(event, useEvent); + } + + @EventHandler(ignoreCancelled = true) + public void onCauldronLevelChange(CauldronLevelChangeEvent event) { + if (event.getEntity() == null) return; + interactDebounce.debounce(event.getBlock(), event.getEntity(), event, + new UseBlockEvent(event, create(event.getEntity()), + event.getBlock()).setAllowed(hasInteractBypass(event.getBlock()))); + } + + /** + * Handle the right click of a block while an item is held. + * + * @param event the original event + * @param cause the list of cause + * @param item the item + * @param placed the placed block + * @param the event type + */ + private static void handleBlockRightClick(T event, Cause cause, @Nullable ItemStack item, Block clicked, Block placed) { + if (item != null && item.getType() == Material.TNT) { + // Workaround for a bug that allowed TNT to trigger instantly if placed + // next to redstone, without plugins getting the clicked place event + // (not sure if this actually still happens) -- note Jun 2019 - happens with dispensers still, tho not players + Events.fireToCancel(event, new UseBlockEvent(event, cause, clicked.getLocation(), Material.TNT)); + + // Workaround for http://leaky.bukkit.org/issues/1034 + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), Material.TNT)); + return; + } + + // Handle created Minecarts + if (item != null && Materials.isMinecart(item.getType())) { + EntityType entityType = Materials.getRelatedEntity(item.getType()); + if (entityType == null) { + entityType = EntityType.MINECART; + } + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, clicked.getLocation().add(0.5, 0, 0.5), entityType)); + return; + } + + // Handle created boats + if (item != null && Materials.isBoat(item.getType())) { + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.BOAT)); + return; + } + + // Handle created armor stands + if (item != null && item.getType() == Material.ARMOR_STAND) { + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.ARMOR_STAND)); + return; + } + + if (item != null && item.getType() == Material.END_CRYSTAL) { /*&& placed.getType() == Material.BEDROCK) {*/ // in vanilla you can only place them on bedrock but who knows what plugins will add + // may be overprotective as a result, but better than being underprotective + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), EntityType.ENDER_CRYSTAL)); + return; + } + + // Handle created spawn eggs + if (item != null && Materials.isSpawnEgg(item.getType())) { + Events.fireToCancel(event, new SpawnEntityEvent(event, cause, placed.getLocation().add(0.5, 0, 0.5), Materials.getEntitySpawnEgg(item.getType()))); + return; + } + + // handle water/lava placement + if (item != null && (item.getType() == Material.WATER_BUCKET || item.getType() == Material.LAVA_BUCKET)) { + Events.fireToCancel(event, new PlaceBlockEvent(event, cause, placed.getLocation(), + item.getType() == Material.WATER_BUCKET ? Material.WATER : Material.LAVA)); + return; + } + } + + private static void handleInventoryHolderUse(T originalEvent, Cause cause, InventoryHolder holder) { + if (originalEvent.isCancelled()) { + return; + } + + if (holder instanceof Entity) { + Entity entity = (Entity) holder; + Material mat = Materials.getRelatedMaterial((entity).getType()); + UseEntityEvent useEntityEvent = new UseEntityEvent(originalEvent, cause, entity); + if (mat != null && hasInteractBypass((entity).getWorld(), mat)) { + useEntityEvent.setAllowed(true); + } + Events.fireToCancel(originalEvent, useEntityEvent); + } else { + if (holder instanceof BlockState) { + final BlockState block = (BlockState) holder; + final UseBlockEvent useBlockEvent = new UseBlockEvent(originalEvent, cause, block.getBlock()); + if (hasInteractBypass(block.getWorld(), block.getType())) { + useBlockEvent.setAllowed(true); + } + Events.fireToCancel(originalEvent, useBlockEvent); + } else if (holder instanceof DoubleChest) { + InventoryHolder left = ((DoubleChest) holder).getLeftSide(); + InventoryHolder right = ((DoubleChest) holder).getRightSide(); + if (left instanceof Chest) { + Events.fireToCancel(originalEvent, new UseBlockEvent(originalEvent, cause, ((Chest) left).getBlock())); + } + if (right instanceof Chest) { + Events.fireToCancel(originalEvent, new UseBlockEvent(originalEvent, cause, ((Chest) right).getBlock())); + } + } + } + } + + private static boolean hasInteractBypass(Block block) { + return getWorldConfig(block.getWorld()).allowAllInteract.test(block); + } + + private static boolean hasInteractBypass(World world, Material material) { + return getWorldConfig(world).allowAllInteract.test(material); + } + + private static boolean hasInteractBypass(World world, ItemStack item) { + return getWorldConfig(world).allowAllInteract.test(item); + } + + private static boolean isBlockModifiedOnClick(Block block, boolean rightClick) { + return Materials.isBlockModifiedOnClick(block.getType(), rightClick) && !hasInteractBypass(block); + } + + private static boolean isItemAppliedToBlock(ItemStack item, Block clicked) { + return Materials.isItemAppliedToBlock(item.getType(), clicked.getType()) + && !hasInteractBypass(clicked) + && !hasInteractBypass(clicked.getWorld(), item); + } + + private static void playDenyEffect(Player player, Location location) { + //player.playSound(location, Sound.SUCCESSFUL_HIT, 0.2f, 0.4f); + if (getConfig().particleEffects) { + player.playEffect(location, Effect.SMOKE, BlockFace.UP); + } + } + + private static void playDenyEffect(Location location) { + if (getConfig().particleEffects) { + location.getWorld().playEffect(location, Effect.SMOKE, BlockFace.UP); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/InvincibilityListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/InvincibilityListener.java new file mode 100644 index 000000000..6a8ec9d04 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/InvincibilityListener.java @@ -0,0 +1,113 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Tameable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; + +public class InvincibilityListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public InvincibilityListener(WorldGuardPlugin plugin) { + super(plugin); + } + + /** + * Test whether a player should be invincible. + * + * @param player The player + * @return True if invincible + */ + private boolean isInvincible(LocalPlayer player) { + return WorldGuard.getInstance().getPlatform().getSessionManager().get(player).isInvincible(player); + } + + @EventHandler(ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + Entity victim = event.getEntity(); + + if (victim instanceof Player) { + Player player = (Player) victim; + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (isInvincible(localPlayer)) { + player.setFireTicks(0); + event.setCancelled(true); + + if (event instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent byEntityEvent = (EntityDamageByEntityEvent) event; + Entity attacker = byEntityEvent.getDamager(); + + if (attacker instanceof Projectile && ((Projectile) attacker).getShooter() instanceof Entity) { + attacker = (Entity) ((Projectile) attacker).getShooter(); + } + + if (getWorldConfig(player.getWorld()).regionInvinciblityRemovesMobs + && attacker instanceof LivingEntity && !(attacker instanceof Player) + && !(attacker instanceof Tameable && ((Tameable) attacker).isTamed())) { + attacker.remove(); + } + } + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onEntityCombust(EntityCombustEvent event) { + Entity entity = event.getEntity(); + + if (entity instanceof Player) { + Player player = (Player) entity; + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (isInvincible(localPlayer)) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onFoodLevelChange(FoodLevelChangeEvent event) { + if (event.getEntity() instanceof Player) { + Player player = (Player) event.getEntity(); + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (event.getFoodLevel() < player.getFoodLevel() && isInvincible(localPlayer)) { + event.setCancelled(true); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerModesListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerModesListener.java new file mode 100644 index 000000000..41837f38e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerModesListener.java @@ -0,0 +1,83 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.player.ProcessPlayerEvent; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.handler.GodMode; +import com.sk89q.worldguard.session.handler.WaterBreathing; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class PlayerModesListener extends AbstractListener { + + private static final Logger log = Logger.getLogger(PlayerModesListener.class.getCanonicalName()); + + private static final String INVINCIBLE_PERMISSION = "worldguard.auto-invincible"; + private static final String INVINCIBLE_GROUP = "wg-invincible"; + private static final String AMPHIBIOUS_GROUP = "wg-amphibious"; + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public PlayerModesListener(WorldGuardPlugin plugin) { + super(plugin); + } + + private boolean hasGodModeGroup(Player player) { + return getConfig().useGodGroup && getPlugin().inGroup(player, INVINCIBLE_GROUP); + } + + private boolean hasGodModePermission(Player player) { + return getConfig().useGodPermission && getPlugin().hasPermission(player, INVINCIBLE_PERMISSION); + } + + private boolean hasAmphibiousGroup(Player player) { + return getConfig().useAmphibiousGroup && getPlugin().inGroup(player, AMPHIBIOUS_GROUP); + } + + @EventHandler + public void onProcessPlayer(ProcessPlayerEvent event) { + Player player = event.getPlayer(); + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(localPlayer); + + if (hasGodModeGroup(player) || hasGodModePermission(player)) { + if (GodMode.set(localPlayer, session, true)) { + log.log(Level.INFO, "Enabled auto-god mode for " + player.getName()); + } + } + + if (hasAmphibiousGroup(player)) { + if (WaterBreathing.set(localPlayer, session, true)) { + log.log(Level.INFO, "Enabled water breathing mode for " + player.getName() + " (player is in group 'wg-amphibious')"); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerMoveListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerMoveListener.java new file mode 100644 index 000000000..45185bad6 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/PlayerMoveListener.java @@ -0,0 +1,166 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import io.papermc.lib.PaperLib; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Horse; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.vehicle.VehicleEnterEvent; +import org.bukkit.plugin.PluginManager; +import org.bukkit.util.Vector; +import org.spigotmc.event.entity.EntityMountEvent; + +public class PlayerMoveListener extends AbstractListener { + + public PlayerMoveListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @Override + public void registerEvents() { + if (WorldGuard.getInstance().getPlatform().getGlobalStateManager().usePlayerMove) { + PluginManager pm = getPlugin().getServer().getPluginManager(); + pm.registerEvents(this, getPlugin()); + if (PaperLib.isSpigot()) { + pm.registerEvents(new EntityMountListener(), getPlugin()); + } + } + } + + @EventHandler + public void onPlayerRespawn(PlayerRespawnEvent event) { + LocalPlayer player = getPlugin().wrapPlayer(event.getPlayer()); + + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + session.testMoveTo(player, BukkitAdapter.adapt(event.getRespawnLocation()), MoveType.RESPAWN, true); + } + + @EventHandler + public void onVehicleEnter(VehicleEnterEvent event) { + Entity entity = event.getEntered(); + if (entity instanceof Player) { + LocalPlayer player = getPlugin().wrapPlayer((Player) entity); + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + if (null != session.testMoveTo(player, BukkitAdapter.adapt(event.getVehicle().getLocation()), MoveType.EMBARK, true)) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getBlockX() == to.getBlockX() + && from.getBlockY() == to.getBlockY() + && from.getBlockZ() == to.getBlockZ()) { + return; + } + + final Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(localPlayer); + MoveType moveType = MoveType.MOVE; + if (event.getPlayer().isGliding()) { + moveType = MoveType.GLIDE; + } else if (event.getPlayer().isSwimming()) { + moveType = MoveType.SWIM; + } else if (event.getPlayer().getVehicle() != null && event.getPlayer().getVehicle() instanceof Horse) { + moveType = MoveType.RIDE; + } + com.sk89q.worldedit.util.Location weLocation = session.testMoveTo(localPlayer, BukkitAdapter.adapt(to), moveType); + + if (weLocation != null) { + final Location override = BukkitAdapter.adapt(weLocation); + override.setX(override.getBlockX() + 0.5); + override.setY(override.getBlockY()); + override.setZ(override.getBlockZ() + 0.5); + override.setPitch(to.getPitch()); + override.setYaw(to.getYaw()); + + event.setTo(override.clone()); + + Entity vehicle = player.getVehicle(); + if (vehicle != null) { + vehicle.eject(); + + Entity current = vehicle; + while (current != null) { + current.eject(); + vehicle.setVelocity(new Vector()); + if (vehicle instanceof LivingEntity) { + vehicle.teleport(override.clone()); + } else { + vehicle.teleport(override.clone().add(0, 1, 0)); + } + current = current.getVehicle(); + } + + player.teleport(override.clone().add(0, 1, 0)); + + Bukkit.getScheduler().runTaskLater(getPlugin(), () -> player.teleport(override.clone().add(0, 1, 0)), 1); + } + } + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + final Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(localPlayer); + com.sk89q.worldedit.util.Location loc = session.testMoveTo(localPlayer, + BukkitAdapter.adapt(event.getPlayer().getLocation()), MoveType.OTHER_CANCELLABLE); // white lie + if (loc != null) { + player.teleport(BukkitAdapter.adapt(loc)); + } + } + + private class EntityMountListener implements Listener { + @EventHandler + public void onEntityMount(EntityMountEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof Player) { + LocalPlayer player = getPlugin().wrapPlayer((Player) entity); + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + if (null != session.testMoveTo(player, BukkitAdapter.adapt(event.getMount().getLocation()), MoveType.EMBARK, true)) { + event.setCancelled(true); + } + } + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionFlagsListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionFlagsListener.java new file mode 100644 index 000000000..e2822f203 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionFlagsListener.java @@ -0,0 +1,153 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; + +import java.util.function.Predicate; + +public class RegionFlagsListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public RegionFlagsListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPlaceBlock(final PlaceBlockEvent event) { + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + + Block block; + if ((block = event.getCause().getFirstBlock()) != null) { + if (Materials.isPistonBlock(block.getType())) { + event.filter(testState(query, Flags.PISTONS), false); + } + } + + if (event.getCause().find(EntityType.SNOWMAN) != null) { + event.filter(testState(query, Flags.SNOWMAN_TRAILS), false); + } + + if (event.getCause().find(EntityType.ENDERMAN) != null) { + event.filter(testState(query, Flags.ENDER_BUILD), false); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onBreakBlock(final BreakBlockEvent event) { + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + + WorldConfiguration config = getWorldConfig(event.getWorld()); + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + + Block block; + if ((block = event.getCause().getFirstBlock()) != null) { + if (Materials.isPistonBlock(block.getType())) { + event.filter(testState(query, Flags.PISTONS), false); + } + } + + if (event.getCause().find(EntityType.CREEPER) != null) { // Creeper + event.filter(testState(query, Flags.CREEPER_EXPLOSION), config.explosionFlagCancellation); + } + + if (event.getCause().find(EntityType.ENDER_DRAGON) != null) { // Enderdragon + event.filter(testState(query, Flags.ENDERDRAGON_BLOCK_DAMAGE), config.explosionFlagCancellation); + } + + if (event.getCause().find(EntityType.ENDER_CRYSTAL) != null) { // EnderCrystal + event.filter(testState(query, Flags.OTHER_EXPLOSION), config.explosionFlagCancellation); + } + + if (event.getCause().find(EntityType.ENDERMAN) != null) { + event.filter(testState(query, Flags.ENDER_BUILD), false); + } + + if (event.getCause().find(EntityType.RAVAGER) != null) { + event.filter(testState(query, Flags.RAVAGER_RAVAGE), false); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + Entity entity = event.getEntity(); + World world = entity.getWorld(); + if (!isRegionSupportEnabled(world)) return; // Region support disabled + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + + if (entity instanceof Player && event.getCause() == DamageCause.FALL) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer((Player) entity); + if (!query.testState(BukkitAdapter.adapt(entity.getLocation()), localPlayer, Flags.FALL_DAMAGE)) { + event.setCancelled(true); + return; + } + } else { + if (entity instanceof Player && event.getCause() == DamageCause.FLY_INTO_WALL) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer((Player) entity); + if (!query.testState(BukkitAdapter.adapt(entity.getLocation()), localPlayer, Flags.FALL_DAMAGE)) { + event.setCancelled(true); + return; + } + } + } + + } + + /** + * Create a new predicate to test a state flag for each location. + * + * @param query the query + * @param flag the flag + * @return a predicate + */ + private Predicate testState(final RegionQuery query, final StateFlag flag) { + return location -> query.testState(BukkitAdapter.adapt(location), (RegionAssociable) null, flag); + } + + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java new file mode 100644 index 000000000..4dad41425 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/RegionProtectionListener.java @@ -0,0 +1,572 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.google.common.base.Predicate; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.cause.Cause; +import com.sk89q.worldguard.bukkit.event.DelegateEvent; +import com.sk89q.worldguard.bukkit.event.block.BreakBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.PlaceBlockEvent; +import com.sk89q.worldguard.bukkit.event.block.UseBlockEvent; +import com.sk89q.worldguard.bukkit.event.entity.DamageEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.DestroyEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.bukkit.event.entity.UseEntityEvent; +import com.sk89q.worldguard.bukkit.internal.WGMetadata; +import com.sk89q.worldguard.bukkit.protection.events.DisallowedPVPEvent; +import com.sk89q.worldguard.bukkit.util.Entities; +import com.sk89q.worldguard.bukkit.util.Events; +import com.sk89q.worldguard.bukkit.util.InteropUtils; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.commands.CommandUtils; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.ExperienceOrb; +import org.bukkit.entity.Item; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; +import org.bukkit.entity.Tameable; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Result; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerTakeLecternBookEvent; +import org.bukkit.event.vehicle.VehicleExitEvent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Handle events that need to be processed by region protection. + */ +public class RegionProtectionListener extends AbstractListener { + + private static final String DENY_MESSAGE_KEY = "worldguard.region.lastMessage"; + private static final String DISEMBARK_MESSAGE_KEY = "worldguard.region.disembarkMessage"; + private static final int LAST_MESSAGE_DELAY = 500; + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public RegionProtectionListener(WorldGuardPlugin plugin) { + super(plugin); + } + + /** + * Tell a sender that s/he cannot do something 'here'. + * + * @param event the event + * @param cause the cause + * @param location the location + * @param what what was done + */ + private void tellErrorMessage(DelegateEvent event, Cause cause, Location location, String what) { + if (event.isSilent() || cause.isIndirect()) { + return; + } + + Object rootCause = cause.getRootCause(); + + if (rootCause instanceof Player) { + Player player = (Player) rootCause; + + long now = System.currentTimeMillis(); + Long lastTime = WGMetadata.getIfPresent(player, DENY_MESSAGE_KEY, Long.class); + if (lastTime == null || now - lastTime >= LAST_MESSAGE_DELAY) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + String message = query.queryValue(BukkitAdapter.adapt(location), localPlayer, Flags.DENY_MESSAGE); + formatAndSendDenyMessage(what, localPlayer, message); + WGMetadata.put(player, DENY_MESSAGE_KEY, now); + } + } + } + + static void formatAndSendDenyMessage(String what, LocalPlayer localPlayer, String message) { + if (message == null || message.isEmpty()) return; + message = WorldGuard.getInstance().getPlatform().getMatcher().replaceMacros(localPlayer, message); + message = CommandUtils.replaceColorMacros(message); + localPlayer.printRaw(message.replace("%what%", what)); + } + + /** + * Return whether the given cause is whitelist (should be ignored). + * + * @param cause the cause + * @param world the world + * @param pvp whether the event in question is PvP combat + * @return true if whitelisted + */ + private boolean isWhitelisted(Cause cause, World world, boolean pvp) { + Object rootCause = cause.getRootCause(); + + if (rootCause instanceof Player) { + Player player = (Player) rootCause; + WorldConfiguration config = getWorldConfig(world); + + if (config.fakePlayerBuildOverride && InteropUtils.isFakePlayer(player)) { + return true; + } + + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + return !pvp && WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, localPlayer.getWorld()); + } else { + return false; + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlaceBlock(final PlaceBlockEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + final Material type = event.getEffectiveMaterial(); + final RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + final RegionAssociable associable = createRegionAssociable(event.getCause()); + + // Don't check liquid flow unless it's enabled + if (event.getCause().getRootCause() instanceof Block + && Materials.isLiquid(type) + && !getWorldConfig(event.getWorld()).checkLiquidFlow) { + return; + } + + event.filter((Predicate) target -> { + boolean canPlace; + String what; + + /* Flint and steel, fire charge, etc. */ + if (Materials.isFire(type)) { + Block block = event.getCause().getFirstBlock(); + boolean fire = block != null && Materials.isFire(type); + boolean lava = block != null && Materials.isLava(block.getType()); + List flags = new ArrayList<>(); + flags.add(Flags.BLOCK_PLACE); + flags.add(Flags.LIGHTER); + if (fire) flags.add(Flags.FIRE_SPREAD); + if (lava) flags.add(Flags.LAVA_FIRE); + canPlace = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, flags.toArray(new StateFlag[flags.size()]))); + what = "place fire"; + + } else if (type == Material.FROSTED_ICE) { + event.setSilent(true); // gets spammy + canPlace = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.BLOCK_PLACE, Flags.FROSTED_ICE_FORM)); + what = "use frostwalker"; // hidden anyway + /* Everything else */ + } else { + canPlace = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.BLOCK_PLACE)); + what = "place that block"; + } + + if (!canPlace) { + tellErrorMessage(event, event.getCause(), target, what); + return false; + } + + return true; + }); + } + + @EventHandler(ignoreCancelled = true) + public void onBreakBlock(final BreakBlockEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + final RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + + if (!event.isCancelled()) { + final RegionAssociable associable = createRegionAssociable(event.getCause()); + + event.filter((Predicate) target -> { + boolean canBreak; + String what; + + /* TNT */ + if (event.getCause().find(EntityType.PRIMED_TNT, EntityType.MINECART_TNT) != null) { + canBreak = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.BLOCK_BREAK, Flags.TNT)); + what = "use dynamite"; + + /* Everything else */ + } else { + canBreak = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.BLOCK_BREAK)); + what = "break that block"; + } + + if (!canBreak) { + tellErrorMessage(event, event.getCause(), target, what); + return false; + } + + return true; + }); + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseBlock(final UseBlockEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + final Material type = event.getEffectiveMaterial(); + final RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + final RegionAssociable associable = createRegionAssociable(event.getCause()); + + event.filter((Predicate) target -> { + boolean canUse; + String what; + + /* Saplings, etc. */ + if (Materials.isConsideredBuildingIfUsed(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event)); + what = "use that"; + + /* Inventory */ + } else if (Materials.isInventoryBlock(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.CHEST_ACCESS)); + what = "open that"; + + /* Inventory for blocks with the possibility to be only use, e.g. lectern */ + } else if (handleAsInventoryUsage(event.getOriginalEvent())) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.CHEST_ACCESS)); + what = "take that"; + + /* Anvils */ + } else if (Materials.isAnvil(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.USE_ANVIL)); + what = "use that"; + + /* Beds */ + } else if (Materials.isBed(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT, Flags.SLEEP)); + what = "sleep"; + + /* Respawn Anchors */ + } else if(type == Material.RESPAWN_ANCHOR) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT, Flags.RESPAWN_ANCHORS)); + what = "use anchors"; + + /* TNT */ + } else if (type == Material.TNT) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT, Flags.TNT)); + what = "use explosives"; + + /* Legacy USE flag */ + } else if (Materials.isUseFlagApplicable(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT, Flags.USE)); + what = "use that"; + + /* Everything else */ + } else { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT)); + what = "use that"; + } + + if (!canUse) { + tellErrorMessage(event, event.getCause(), target, what); + return false; + } + + return true; + }); + } + + @EventHandler(ignoreCancelled = true) + public void onSpawnEntity(SpawnEntityEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + Location target = event.getTarget(); + EntityType type = event.getEffectiveType(); + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + RegionAssociable associable = createRegionAssociable(event.getCause()); + + boolean canSpawn; + String what; + + /* Vehicles */ + if (Entities.isVehicle(type)) { + canSpawn = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.PLACE_VEHICLE)); + what = "place vehicles"; + + /* Item pickup */ + } else if (event.getEntity() instanceof Item) { + canSpawn = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.ITEM_DROP)); + what = "drop items"; + + /* XP drops */ + } else if (type == EntityType.EXPERIENCE_ORB) { + canSpawn = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.EXP_DROPS)); + what = "drop XP"; + + } else if (Entities.isAoECloud(type)) { + canSpawn = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.POTION_SPLASH)); + what = "use lingering potions"; + + /* Everything else */ + } else { + canSpawn = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event)); + what = "place things"; + } + + if (!canSpawn) { + tellErrorMessage(event, event.getCause(), target, what); + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onDestroyEntity(DestroyEntityEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + Location target = event.getTarget(); + EntityType type = event.getEntity().getType(); + RegionAssociable associable = createRegionAssociable(event.getCause()); + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + boolean canDestroy; + String what; + + /* Vehicles */ + if (Entities.isVehicle(type)) { + canDestroy = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.DESTROY_VEHICLE)); + what = "break vehicles"; + + /* Item pickup */ + } else if (event.getEntity() instanceof Item || event.getEntity() instanceof ExperienceOrb) { + canDestroy = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.ITEM_PICKUP)); + what = "pick up items"; + + /* Everything else */ + } else { + canDestroy = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event)); + what = "break things"; + } + + if (!canDestroy) { + tellErrorMessage(event, event.getCause(), target, what); + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onUseEntity(UseEntityEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + if (isWhitelisted(event.getCause(), event.getWorld(), false)) return; // Whitelisted cause + + Location target = event.getTarget(); + RegionAssociable associable = createRegionAssociable(event.getCause()); + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + boolean canUse; + String what; + + /* Hostile / ambient mob override */ + final Entity entity = event.getEntity(); + final EntityType type = entity.getType(); + if (Entities.isHostile(entity) || Entities.isAmbient(entity) + || Entities.isNPC(entity) || entity instanceof Player) { + canUse = event.getRelevantFlags().isEmpty() || query.queryState(BukkitAdapter.adapt(target), associable, combine(event)) != State.DENY; + what = "use that"; + /* Paintings, item frames, etc. */ + } else if (Entities.isConsideredBuildingIfUsed(entity)) { + if (type == EntityType.ITEM_FRAME && event.getCause().getFirstPlayer() != null + && ((ItemFrame) entity).getItem().getType() != Material.AIR) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.ITEM_FRAME_ROTATE)); + what = "change that"; + } else if (Entities.isMinecart(type)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.CHEST_ACCESS)); + what = "open that"; + } else { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event)); + what = "change that"; + } + /* Ridden on use */ + } else if (Entities.isRiddenOnUse(entity)) { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.RIDE, Flags.INTERACT)); + what = "ride that"; + + /* Everything else */ + } else { + canUse = query.testBuild(BukkitAdapter.adapt(target), associable, combine(event, Flags.INTERACT)); + what = "use that"; + } + + if (!canUse) { + tellErrorMessage(event, event.getCause(), target, what); + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onDamageEntity(DamageEntityEvent event) { + if (event.getResult() == Result.ALLOW) return; // Don't care about events that have been pre-allowed + if (!isRegionSupportEnabled(event.getWorld())) return; // Region support disabled + // Whitelist check is below + + com.sk89q.worldedit.util.Location target = BukkitAdapter.adapt(event.getTarget()); + RegionAssociable associable = createRegionAssociable(event.getCause()); + + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + Player playerAttacker = event.getCause().getFirstPlayer(); + boolean canDamage; + String what; + + // Block PvP like normal even if the player has an override permission + // because (1) this is a frequent source of confusion and + // (2) some users want to block PvP even with the bypass permission + boolean pvp = event.getEntity() instanceof Player && playerAttacker != null && !playerAttacker.equals(event.getEntity()); + if (isWhitelisted(event.getCause(), event.getWorld(), pvp)) { + return; + } + + /* Hostile / ambient mob override */ + if (Entities.isHostile(event.getEntity()) || Entities.isAmbient(event.getEntity()) + || Entities.isVehicle(event.getEntity().getType())) { + canDamage = event.getRelevantFlags().isEmpty() || query.queryState(target, associable, combine(event)) != State.DENY; + what = "hit that"; + + /* Paintings, item frames, etc. */ + } else if (Entities.isConsideredBuildingIfUsed(event.getEntity())) { + canDamage = query.testBuild(target, associable, combine(event)); + what = "change that"; + + /* PVP */ + } else if (pvp) { + LocalPlayer localAttacker = WorldGuardPlugin.inst().wrapPlayer(playerAttacker); + Player defender = (Player) event.getEntity(); + + // if defender is an NPC + if (Entities.isNPC(defender)) { + return; + } + + canDamage = query.testBuild(target, associable, combine(event, Flags.PVP)) + && query.queryState(localAttacker.getLocation(), localAttacker, combine(event, Flags.PVP)) != State.DENY + && query.queryState(target, localAttacker, combine(event, Flags.PVP)) != State.DENY; + + // Fire the disallow PVP event + if (!canDamage && Events.fireAndTestCancel(new DisallowedPVPEvent(playerAttacker, defender, event.getOriginalEvent()))) { + canDamage = true; + } + + what = "PvP"; + + /* Player damage not caused by another player */ + } else if (event.getEntity() instanceof Player) { + canDamage = event.getRelevantFlags().isEmpty() || query.queryState(target, associable, combine(event)) != State.DENY; + what = "damage that"; + + /* damage to non-hostile mobs (e.g. animals) */ + } else if (Entities.isNonHostile(event.getEntity())) { + canDamage = query.testBuild(target, associable, combine(event, Flags.DAMAGE_ANIMALS)); + what = "harm that"; + + /* Everything else */ + } else { + canDamage = query.testBuild(target, associable, combine(event, Flags.INTERACT)); + what = "hit that"; + } + + if (!canDamage) { + tellErrorMessage(event, event.getCause(), event.getTarget(), what); + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onVehicleExit(VehicleExitEvent event) { + Entity vehicle = event.getVehicle(); + if (!isRegionSupportEnabled(vehicle.getWorld())) return; // Region support disabled + Entity exited = event.getExited(); + + if (vehicle instanceof Tameable && exited instanceof Player) { + Player player = (Player) exited; + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + if (!isWhitelisted(Cause.create(player), vehicle.getWorld(), false)) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + Location location = vehicle.getLocation(); + if (!query.testBuild(BukkitAdapter.adapt(location), localPlayer, Flags.RIDE, Flags.INTERACT)) { + long now = System.currentTimeMillis(); + Long lastTime = WGMetadata.getIfPresent(player, DISEMBARK_MESSAGE_KEY, Long.class); + if (lastTime == null || now - lastTime >= LAST_MESSAGE_DELAY) { + player.sendMessage("" + ChatColor.GOLD + "Don't disembark here!" + ChatColor.GRAY + " You can't get back on."); + WGMetadata.put(player, DISEMBARK_MESSAGE_KEY, now); + } + + event.setCancelled(true); + } + } + } + } + + /** + * Combine the flags from a delegate event with an array of flags. + * + *

The delegate event's flags appear at the end.

+ * + * @param event The event + * @param flag An array of flags + * @return An array of flags + */ + private static StateFlag[] combine(DelegateEvent event, StateFlag... flag) { + List extra = event.getRelevantFlags(); + StateFlag[] flags = Arrays.copyOf(flag, flag.length + extra.size()); + for (int i = 0; i < extra.size(); i++) { + flags[flag.length + i] = extra.get(i); + } + return flags; + } + + /** + * Check if that event should be handled as inventory usage, e.g. if a player takes a book from a lectern + * + * @param event the event to handle + * @return whether it should be handled as inventory usage + */ + private static boolean handleAsInventoryUsage(Event event) { + return event instanceof PlayerTakeLecternBookEvent; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardBlockListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardBlockListener.java new file mode 100644 index 000000000..eabe7646a --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardBlockListener.java @@ -0,0 +1,692 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.util.SpongeUtil; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.entity.Player; +import org.bukkit.entity.Snowman; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockBurnEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockFadeEvent; +import org.bukkit.event.block.BlockFormEvent; +import org.bukkit.event.block.BlockFromToEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.event.block.BlockSpreadEvent; +import org.bukkit.event.block.EntityBlockFormEvent; +import org.bukkit.event.block.LeavesDecayEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * The listener for block events. + */ +public class WorldGuardBlockListener extends AbstractListener { + + + /** + * Construct the object. + * + * @param plugin The plugin instance + */ + public WorldGuardBlockListener(WorldGuardPlugin plugin) { + super(plugin); + } + + /* + * Called when a block is broken. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + Player player = event.getPlayer(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (!wcfg.itemDurability) { + ItemStack held = player.getInventory().getItemInMainHand(); + ItemMeta meta = held.getItemMeta(); + if (meta != null) { + ((Damageable) meta).setDamage(0); + held.setItemMeta(meta); + player.getInventory().setItemInMainHand(held); + } + } + } + + /* + * Called when fluids flow. + */ + @EventHandler(ignoreCancelled = true) + public void onBlockFromTo(BlockFromToEvent event) { + World world = event.getBlock().getWorld(); + Block blockFrom = event.getBlock(); + Block blockTo = event.getToBlock(); + + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + Material fromType = blockFrom.getType(); + boolean isWater = Materials.isWater(fromType); + boolean isLava = fromType == Material.LAVA; + boolean isAir = fromType == Material.AIR; + + WorldConfiguration wcfg = getWorldConfig(world); + + if (wcfg.simulateSponge && isWater) { + int ox = blockTo.getX(); + int oy = blockTo.getY(); + int oz = blockTo.getZ(); + + for (int cx = -wcfg.spongeRadius; cx <= wcfg.spongeRadius; cx++) { + for (int cy = -wcfg.spongeRadius; cy <= wcfg.spongeRadius; cy++) { + for (int cz = -wcfg.spongeRadius; cz <= wcfg.spongeRadius; cz++) { + Block sponge = world.getBlockAt(ox + cx, oy + cy, oz + cz); + if (sponge.getType() == Material.SPONGE + && (!wcfg.redstoneSponges || !sponge.isBlockIndirectlyPowered())) { + event.setCancelled(true); + return; + } + } + } + } + } + + /*if (plugin.classicWater && isWater) { + int blockBelow = blockFrom.getRelative(0, -1, 0).getTypeId(); + if (blockBelow != 0 && blockBelow != 8 && blockBelow != 9) { + blockFrom.setTypeId(9); + if (blockTo.getTypeId() == 0) { + blockTo.setTypeId(9); + } + return; + } + }*/ + + // Check the fluid block (from) whether it is air. + // If so and the target block is protected, cancel the event + if (!wcfg.preventWaterDamage.isEmpty()) { + Material targetId = blockTo.getType(); + + if ((isAir || isWater) && + wcfg.preventWaterDamage.contains(BukkitAdapter.asBlockType(targetId).getId())) { + event.setCancelled(true); + return; + } + } + + if (!wcfg.allowedLavaSpreadOver.isEmpty() && isLava) { + Material targetId = blockTo.getRelative(0, -1, 0).getType(); + + if (!wcfg.allowedLavaSpreadOver.contains(BukkitAdapter.asBlockType(targetId).getId())) { + event.setCancelled(true); + return; + } + } + + if (wcfg.highFreqFlags && (isWater || blockFrom.getBlockData() instanceof Waterlogged) + && WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(blockFrom.getLocation()), (RegionAssociable) null, Flags.WATER_FLOW) == StateFlag.State.DENY) { + event.setCancelled(true); + return; + } + + if (wcfg.highFreqFlags && isLava + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(blockFrom.getLocation()), (RegionAssociable) null, Flags.LAVA_FLOW))) { + event.setCancelled(true); + return; + } + } + + /* + * Called when a block gets ignited. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockIgnite(BlockIgniteEvent event) { + IgniteCause cause = event.getCause(); + Block block = event.getBlock(); + World world = block.getWorld(); + + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(world); + boolean isFireSpread = cause == IgniteCause.SPREAD; + + if (wcfg.preventLightningFire && cause == IgniteCause.LIGHTNING) { + event.setCancelled(true); + return; + } + + if (wcfg.preventLavaFire && cause == IgniteCause.LAVA) { + event.setCancelled(true); + return; + } + + if (wcfg.disableFireSpread && isFireSpread) { + event.setCancelled(true); + return; + } + + if (wcfg.blockLighter && (cause == IgniteCause.FLINT_AND_STEEL || cause == IgniteCause.FIREBALL) + && event.getPlayer() != null + && !getPlugin().hasPermission(event.getPlayer(), "worldguard.override.lighter")) { + event.setCancelled(true); + return; + } + + if (wcfg.fireSpreadDisableToggle && isFireSpread) { + event.setCancelled(true); + return; + } + + if (!wcfg.disableFireSpreadBlocks.isEmpty() && isFireSpread) { + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); + + if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y - 1, z).getType()).getId()) + || wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x + 1, y, z).getType()).getId()) + || wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x - 1, y, z).getType()).getId()) + || wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z - 1).getType()).getId()) + || wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(world.getBlockAt(x, y, z + 1).getType()).getId())) { + event.setCancelled(true); + return; + } + } + + if (wcfg.useRegions) { + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(block.getLocation())); + + if (wcfg.highFreqFlags && isFireSpread + && !set.testState(null, Flags.FIRE_SPREAD)) { + event.setCancelled(true); + return; + } + + if (wcfg.highFreqFlags && cause == IgniteCause.LAVA + && !set.testState(null, Flags.LAVA_FIRE)) { + event.setCancelled(true); + return; + } + + if (cause == IgniteCause.FIREBALL && event.getPlayer() == null) { + // wtf bukkit, FIREBALL is supposed to be reserved to players + if (!set.testState(null, Flags.GHAST_FIREBALL)) { + event.setCancelled(true); + return; + } + } + + if (cause == IgniteCause.LIGHTNING && !set.testState(null, Flags.LIGHTNING)) { + event.setCancelled(true); + return; + } + } + } + + /* + * Called when a block is destroyed from burning. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockBurn(BlockBurnEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + BukkitWorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + if (wcfg.disableFireSpread) { + event.setCancelled(true); + return; + } + + if (wcfg.fireSpreadDisableToggle) { + Block block = event.getBlock(); + event.setCancelled(true); + checkAndDestroyFireAround(block.getWorld(), block.getX(), block.getY(), block.getZ()); + return; + } + + if (!wcfg.disableFireSpreadBlocks.isEmpty()) { + Block block = event.getBlock(); + + if (wcfg.disableFireSpreadBlocks.contains(BukkitAdapter.asBlockType(block.getType()).getId())) { + event.setCancelled(true); + checkAndDestroyFireAround(block.getWorld(), block.getX(), block.getY(), block.getZ()); + return; + } + } + + if (wcfg.isChestProtected(BukkitAdapter.adapt(event.getBlock().getLocation()))) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions) { + Block block = event.getBlock(); + int x = block.getX(); + int y = block.getY(); + int z = block.getZ(); + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(block.getLocation())); + + if (!set.testState(null, Flags.FIRE_SPREAD)) { + checkAndDestroyFireAround(block.getWorld(), x, y, z); + event.setCancelled(true); + } + + } + } + + private void checkAndDestroyFireAround(World world, int x, int y, int z) { + checkAndDestroyFire(world, x, y, z + 1); + checkAndDestroyFire(world, x, y, z - 1); + checkAndDestroyFire(world, x, y + 1, z); + checkAndDestroyFire(world, x, y - 1, z); + checkAndDestroyFire(world, x + 1, y, z); + checkAndDestroyFire(world, x - 1, y, z); + } + + private void checkAndDestroyFire(World world, int x, int y, int z) { + if (Materials.isFire(world.getBlockAt(x, y, z).getType())) { + world.getBlockAt(x, y, z).setType(Material.AIR); + } + } + + /* + * Called when block physics occurs. + */ + @EventHandler(ignoreCancelled = true) + public void onBlockPhysics(BlockPhysicsEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + final Material id = event.getBlock().getType(); + + if (id == Material.GRAVEL && wcfg.noPhysicsGravel) { + event.setCancelled(true); + return; + } + + if (id == Material.SAND && wcfg.noPhysicsSand) { + event.setCancelled(true); + return; + } + + if (id == Material.NETHER_PORTAL && wcfg.allowPortalAnywhere) { + event.setCancelled(true); + return; + } + + if (id == Material.LADDER && wcfg.ropeLadders) { + if (event.getBlock().getRelative(0, 1, 0).getType() == Material.LADDER) { + event.setCancelled(true); + return; + } + } + } + + /* + * Called when a player places a block. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + Block target = event.getBlock(); + World world = target.getWorld(); + + WorldConfiguration wcfg = getWorldConfig(world); + + if (wcfg.simulateSponge && target.getType() == Material.SPONGE) { + if (wcfg.redstoneSponges && target.isBlockIndirectlyPowered()) { + return; + } + + int ox = target.getX(); + int oy = target.getY(); + int oz = target.getZ(); + + SpongeUtil.clearSpongeWater(BukkitAdapter.adapt(world), ox, oy, oz); + } + } + + /* + * Called when redstone changes. + */ + @EventHandler(priority = EventPriority.HIGH) + public void onBlockRedstoneChange(BlockRedstoneEvent event) { + Block blockTo = event.getBlock(); + World world = blockTo.getWorld(); + + WorldConfiguration wcfg = getWorldConfig(world); + + if (wcfg.simulateSponge && wcfg.redstoneSponges) { + int ox = blockTo.getX(); + int oy = blockTo.getY(); + int oz = blockTo.getZ(); + + for (int cx = -1; cx <= 1; cx++) { + for (int cy = -1; cy <= 1; cy++) { + for (int cz = -1; cz <= 1; cz++) { + Block sponge = world.getBlockAt(ox + cx, oy + cy, oz + cz); + if (sponge.getType() == Material.SPONGE + && sponge.isBlockIndirectlyPowered()) { + SpongeUtil.clearSpongeWater(BukkitAdapter.adapt(world), ox + cx, oy + cy, oz + cz); + } else if (sponge.getType() == Material.SPONGE + && !sponge.isBlockIndirectlyPowered()) { + SpongeUtil.addSpongeWater(BukkitAdapter.adapt(world), ox + cx, oy + cy, oz + cz); + } + } + } + } + + return; + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onLeavesDecay(LeavesDecayEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + if (wcfg.disableLeafDecay) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions) { + if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.LEAF_DECAY))) { + event.setCancelled(true); + } + } + } + + /* + * Called when a block is formed based on world conditions. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockForm(BlockFormEvent event) { + ConfigurationManager cfg = getConfig(); + + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + Material type = event.getNewState().getType(); + + if (event instanceof EntityBlockFormEvent) { + if (((EntityBlockFormEvent) event).getEntity() instanceof Snowman) { + if (wcfg.disableSnowmanTrails) { + event.setCancelled(true); + return; + } + } + return; + } + + if (type == Material.ICE) { + if (wcfg.disableIceFormation) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.ICE_FORM))) { + event.setCancelled(true); + return; + } + } + + if (type == Material.SNOW) { + if (wcfg.disableSnowFormation) { + event.setCancelled(true); + return; + } + if (!wcfg.allowedSnowFallOver.isEmpty()) { + Material targetId = event.getBlock().getRelative(0, -1, 0).getType(); + + if (!wcfg.allowedSnowFallOver.contains(BukkitAdapter.asBlockType(targetId).getId())) { + event.setCancelled(true); + return; + } + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.SNOW_FALL))) { + event.setCancelled(true); + return; + } + } + } + + /* + * Called when a block spreads based on world conditions. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockSpread(BlockSpreadEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + Material newType = event.getNewState().getType(); // craftbukkit randomly gives AIR as event.getSource even if that block is not air + + if (Materials.isMushroom(newType)) { + if (wcfg.disableMushroomSpread) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.MUSHROOMS))) { + event.setCancelled(true); + return; + } + } + + if (newType == Material.GRASS_BLOCK) { + if (wcfg.disableGrassGrowth) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.GRASS_SPREAD))) { + event.setCancelled(true); + return; + } + } + + if (newType == Material.MYCELIUM) { + if (wcfg.disableMyceliumSpread) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.MYCELIUM_SPREAD))) { + event.setCancelled(true); + return; + } + } + + if (Materials.isVine(newType)) { + if (wcfg.disableVineGrowth) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.VINE_GROWTH))) { + event.setCancelled(true); + return; + } + } + + handleGrow(event, event.getBlock().getLocation(), newType); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockGrow(BlockGrowEvent event) { + Location loc = event.getBlock().getLocation(); + final Material type = event.getNewState().getType(); + + handleGrow(event, loc, type); + } + + private void handleGrow(Cancellable event, Location loc, Material type) { + WorldConfiguration wcfg = getWorldConfig(loc.getWorld()); + if (Materials.isCrop(type)) { + if (wcfg.disableCropGrowth) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(loc), (RegionAssociable) null, Flags.CROP_GROWTH))) { + event.setCancelled(true); + return; + } + } + } + + /* + * Called when a block fades. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onBlockFade(BlockFadeEvent event) { + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + + if (event.getBlock().getType() == Material.ICE) { + if (wcfg.disableIceMelting) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.ICE_MELT))) { + event.setCancelled(true); + return; + } + } else if (event.getBlock().getType() == Material.FROSTED_ICE) { + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.FROSTED_ICE_MELT))) { + event.setCancelled(true); + return; + } + } else if (event.getBlock().getType() == Material.SNOW) { + if (wcfg.disableSnowMelting) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.SNOW_MELT))) { + event.setCancelled(true); + return; + } + } else if (event.getBlock().getType() == Material.FARMLAND) { + if (wcfg.disableSoilDehydration) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.SOIL_DRY))) { + event.setCancelled(true); + return; + } + } else if (Materials.isCoral(event.getBlock().getType())) { + if (wcfg.disableCoralBlockFade) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(event.getBlock().getLocation()), (RegionAssociable) null, Flags.CORAL_FADE))) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onBlockExplode(BlockExplodeEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getBlock().getWorld()); + if (wcfg.blockOtherExplosions) { + event.setCancelled(true); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCommandBookListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCommandBookListener.java new file mode 100644 index 000000000..8af947eca --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardCommandBookListener.java @@ -0,0 +1,79 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.commandbook.InfoComponent; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +/** + * @author zml2008 + */ +public class WorldGuardCommandBookListener implements Listener { + private final WorldGuardPlugin plugin; + + public WorldGuardCommandBookListener(WorldGuardPlugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerWhois(InfoComponent.PlayerWhoisEvent event) { + if (event.getPlayer() instanceof Player) { + Player player = (Player) event.getPlayer(); + LocalPlayer localPlayer = plugin.wrapPlayer(player); + if (WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(localPlayer.getWorld()).useRegions) { + ApplicableRegionSet regions = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()); + + // Current regions + StringBuilder regionStr = new StringBuilder(); + boolean first = true; + + for (ProtectedRegion region : regions) { + if (!first) { + regionStr.append(", "); + } + + if (region.isOwner(localPlayer)) { + regionStr.append("+"); + } else if (region.isMemberOnly(localPlayer)) { + regionStr.append("-"); + } + + regionStr.append(region.getId()); + + first = false; + } + + if (regions.size() > 0) { + event.addWhoisInformation("Current Regions", regionStr); + } + event.addWhoisInformation("Can build", regions.testState(localPlayer, Flags.BUILD)); + } + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java new file mode 100644 index 000000000..f2fd6801c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardEntityListener.java @@ -0,0 +1,853 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.BukkitWorldConfiguration; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.util.Entities; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Enderman; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.FallingBlock; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.Wither; +import org.bukkit.entity.WitherSkull; +import org.bukkit.entity.Wolf; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.CreeperPowerEvent; +import org.bukkit.event.entity.EntityBreakDoorEvent; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityDamageByBlockEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntityInteractEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason; +import org.bukkit.event.entity.EntityTransformEvent; +import org.bukkit.event.entity.ExplosionPrimeEvent; +import org.bukkit.event.entity.FoodLevelChangeEvent; +import org.bukkit.event.entity.PigZapEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.vehicle.VehicleEnterEvent; +import org.bukkit.event.world.PortalCreateEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.projectiles.ProjectileSource; + +import java.util.Set; + +/** + * Listener for entity related events. + */ +public class WorldGuardEntityListener extends AbstractListener { + + /** + * Construct the object; + * + * @param plugin The plugin instance + */ + public WorldGuardEntityListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityInteract(EntityInteractEvent event) { + Block block = event.getBlock(); + + WorldConfiguration wcfg = getWorldConfig(block.getWorld()); + + if (block.getType() == Material.FARMLAND && wcfg.disableCreatureCropTrampling) { + event.setCancelled(true); + return; + } + if (block.getType() == Material.TURTLE_EGG && wcfg.disableCreatureTurtleEggTrampling) { + event.setCancelled(true); + return; + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onEntityDeath(EntityDeathEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getEntity().getWorld()); + + if (event instanceof PlayerDeathEvent && wcfg.disableDeathMessages) { + ((PlayerDeathEvent) event).setDeathMessage(""); + } + } + + private void onEntityDamageByBlock(EntityDamageByBlockEvent event) { + Entity defender = event.getEntity(); + DamageCause type = event.getCause(); + + WorldConfiguration wcfg = getWorldConfig(defender.getWorld()); + + if (defender instanceof Wolf && ((Wolf) defender).isTamed()) { + if (wcfg.antiWolfDumbness && !(type == DamageCause.VOID)) { + event.setCancelled(true); + return; + } + } else if (defender instanceof Player) { + Player player = (Player) defender; + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (wcfg.disableLavaDamage && type == DamageCause.LAVA) { + event.setCancelled(true); + player.setFireTicks(0); + return; + } + + if (wcfg.disableContactDamage && type == DamageCause.CONTACT) { + event.setCancelled(true); + return; + } + + if (wcfg.teleportOnVoid && type == DamageCause.VOID) { + localPlayer.findFreePosition(); + if (wcfg.safeFallOnVoid) { + localPlayer.resetFallDistance(); + } + event.setCancelled(true); + return; + } + + if (wcfg.disableVoidDamage && type == DamageCause.VOID) { + event.setCancelled(true); + return; + } + + if (type == DamageCause.BLOCK_EXPLOSION + && (wcfg.disableExplosionDamage || wcfg.blockOtherExplosions + || (wcfg.explosionFlagCancellation + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(localPlayer.getLocation(), (RegionAssociable) null, Flags.OTHER_EXPLOSION))))) { + event.setCancelled(true); + return; + } + } else { + + // for whatever reason, plugin-caused explosions with a null entity count as block explosions and aren't + // handled anywhere else + if (type == DamageCause.BLOCK_EXPLOSION + && (wcfg.blockOtherExplosions + || ((wcfg.explosionFlagCancellation || Entities.isConsideredBuildingIfUsed(defender)) + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery() + .queryState(BukkitAdapter.adapt(defender.getLocation()), (RegionAssociable) null, Flags.OTHER_EXPLOSION))))) { + event.setCancelled(true); + return; + + } + } + } + + private void onEntityDamageByEntity(EntityDamageByEntityEvent event) { + + if (event.getDamager() instanceof Projectile) { + onEntityDamageByProjectile(event); + return; + } + + Entity attacker = event.getDamager(); + Entity defender = event.getEntity(); + + WorldConfiguration wcfg = getWorldConfig(defender.getWorld()); + + if (defender instanceof ItemFrame) { + if (checkItemFrameProtection(attacker, (ItemFrame) defender)) { + event.setCancelled(true); + return; + } + } else if (defender instanceof ArmorStand && !(attacker instanceof Player)) { + if (wcfg.blockEntityArmorStandDestroy) { + event.setCancelled(true); + return; + } + } + + if (attacker instanceof EnderCrystal) { + // this isn't handled elsewhere because ender crystal explosions don't carry a player cause + // in the same way that creepers or tnt can + if (wcfg.useRegions && wcfg.explosionFlagCancellation) { + if (!WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(defender.getLocation())) + .testState(null, Flags.OTHER_EXPLOSION)) { + event.setCancelled(true); + return; + } + } + } + + if (defender instanceof Player) { + Player player = (Player) defender; + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + if (wcfg.disableLightningDamage && event.getCause() == DamageCause.LIGHTNING) { + event.setCancelled(true); + return; + } + + if (wcfg.disableExplosionDamage) { + switch (event.getCause()) { + case BLOCK_EXPLOSION: + case ENTITY_EXPLOSION: + event.setCancelled(true); + return; + } + } + + if (attacker != null) { + if (attacker instanceof TNTPrimed || attacker instanceof ExplosiveMinecart) { + // The check for explosion damage should be handled already... But... What ever... + if (wcfg.blockTNTExplosions) { + event.setCancelled(true); + return; + } + } + + if (attacker instanceof LivingEntity && !(attacker instanceof Player)) { + if (attacker instanceof Creeper && wcfg.blockCreeperExplosions) { + event.setCancelled(true); + return; + } + + if (wcfg.disableMobDamage) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions) { + ApplicableRegionSet set = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()); + + if (!set.testState(localPlayer, Flags.MOB_DAMAGE) && !(attacker instanceof Tameable)) { + event.setCancelled(true); + return; + } + } + } + } + } + } + + private void onEntityDamageByProjectile(EntityDamageByEntityEvent event) { + Entity defender = event.getEntity(); + Entity attacker; + ProjectileSource source = ((Projectile) event.getDamager()).getShooter(); + if (source instanceof LivingEntity) { + attacker = (LivingEntity) source; + } else { + return; + } + + WorldConfiguration wcfg = getWorldConfig(defender.getWorld()); + if (defender instanceof Player) { + Player player = (Player) defender; + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + + // Check Mob + if (!(attacker instanceof Player)) { + if (wcfg.disableMobDamage) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions) { + if (!WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()).testState(localPlayer, Flags.MOB_DAMAGE)) { + event.setCancelled(true); + return; + } + } + if (event.getDamager() instanceof Fireball) { + Fireball fireball = (Fireball) event.getDamager(); + if (fireball instanceof WitherSkull) { + if (wcfg.blockWitherSkullExplosions) { + event.setCancelled(true); + return; + } + } else { + if (wcfg.blockFireballExplosions) { + event.setCancelled(true); + return; + } + } + if (wcfg.useRegions) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + if (!query.testState(localPlayer.getLocation(), localPlayer, Flags.GHAST_FIREBALL) && wcfg.explosionFlagCancellation) { + event.setCancelled(true); + return; + } + + } + } + } + } else if (defender instanceof ItemFrame) { + if (checkItemFrameProtection(attacker, (ItemFrame) defender)) { + event.setCancelled(true); + return; + } + } else if (defender instanceof ArmorStand && Entities.isNonPlayerCreature(attacker)) { + if (wcfg.blockEntityArmorStandDestroy) { + event.setCancelled(true); + } + } + + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityDamage(EntityDamageEvent event) { + + if (event instanceof EntityDamageByEntityEvent) { + this.onEntityDamageByEntity((EntityDamageByEntityEvent) event); + return; + } else if (event instanceof EntityDamageByBlockEvent) { + this.onEntityDamageByBlock((EntityDamageByBlockEvent) event); + return; + } + + Entity defender = event.getEntity(); + DamageCause type = event.getCause(); + + WorldConfiguration wcfg = getWorldConfig(defender.getWorld()); + + if (defender instanceof Wolf && ((Wolf) defender).isTamed()) { + if (wcfg.antiWolfDumbness) { + event.setCancelled(true); + return; + } + } else if (defender instanceof Player) { + Player player = (Player) defender; + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + + if (type == DamageCause.WITHER) { + // wither boss DoT tick + if (wcfg.disableMobDamage) { + event.setCancelled(true); + return; + } + + if (wcfg.useRegions) { + ApplicableRegionSet set = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()); + + if (!set.testState(getPlugin().wrapPlayer(player), Flags.MOB_DAMAGE)) { + event.setCancelled(true); + return; + } + } + } + + if (type == DamageCause.DROWNING && getConfig().hasAmphibiousMode(localPlayer)) { + player.setRemainingAir(player.getMaximumAir()); + event.setCancelled(true); + return; + } + + ItemStack helmet = player.getInventory().getHelmet(); + + if (type == DamageCause.DROWNING && wcfg.pumpkinScuba + && helmet != null + && (helmet.getType() == Material.CARVED_PUMPKIN + || helmet.getType() == Material.JACK_O_LANTERN)) { + player.setRemainingAir(player.getMaximumAir()); + event.setCancelled(true); + return; + } + + if (wcfg.disableFallDamage && type == DamageCause.FALL) { + event.setCancelled(true); + return; + } + + if (wcfg.disableFireDamage && (type == DamageCause.FIRE + || type == DamageCause.FIRE_TICK)) { + event.setCancelled(true); + return; + } + + if (wcfg.disableDrowningDamage && type == DamageCause.DROWNING) { + player.setRemainingAir(player.getMaximumAir()); + event.setCancelled(true); + return; + } + + if (wcfg.teleportOnSuffocation && type == DamageCause.SUFFOCATION) { + localPlayer.findFreePosition(); + event.setCancelled(true); + return; + } + + if (wcfg.disableSuffocationDamage && type == DamageCause.SUFFOCATION) { + event.setCancelled(true); + return; + } + } + } + + /* + * Called on entity explode. + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityExplode(EntityExplodeEvent event) { + ConfigurationManager cfg = getConfig(); + Entity ent = event.getEntity(); + + if (cfg.activityHaltToggle) { + ent.remove(); + event.setCancelled(true); + return; + } + + BukkitWorldConfiguration wcfg = getWorldConfig(event.getLocation().getWorld()); + if (ent instanceof Creeper) { + if (wcfg.blockCreeperExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.blockCreeperBlockDamage) { + event.blockList().clear(); + return; + } + } else if (ent instanceof EnderDragon) { + if (wcfg.blockEnderDragonBlockDamage) { + event.blockList().clear(); + return; + } + } else if (ent instanceof TNTPrimed || ent instanceof ExplosiveMinecart) { + if (wcfg.blockTNTExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.blockTNTBlockDamage) { + event.blockList().clear(); + return; + } + } else if (ent instanceof Fireball) { + if (ent instanceof WitherSkull) { + if (wcfg.blockWitherSkullExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.blockWitherSkullBlockDamage) { + event.blockList().clear(); + return; + } + } else { + if (wcfg.blockFireballExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.blockFireballBlockDamage) { + event.blockList().clear(); + return; + } + } + // allow wither skull blocking since there is no dedicated flag atm + if (wcfg.useRegions) { + for (Block block : event.blockList()) { + if (!WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(block.getLocation())).testState(null, Flags.GHAST_FIREBALL)) { + event.blockList().clear(); + if (wcfg.explosionFlagCancellation) event.setCancelled(true); + return; + } + } + } + } else if (ent instanceof Wither) { + if (wcfg.blockWitherExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.blockWitherBlockDamage) { + event.blockList().clear(); + return; + } + if (wcfg.useRegions) { + for (Block block : event.blockList()) { + if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(block.getLocation()), + (RegionAssociable) null, Flags.WITHER_DAMAGE))) { + event.blockList().clear(); + event.setCancelled(true); + return; + } + } + } + } else { + // unhandled entity + if (wcfg.blockOtherExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions) { + for (Block block : event.blockList()) { + if (!WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(block.getLocation())).testState(null, Flags.OTHER_EXPLOSION)) { + event.blockList().clear(); + if (wcfg.explosionFlagCancellation) event.setCancelled(true); + return; + } + } + } + } + + + if (wcfg.signChestProtection) { + for (Block block : event.blockList()) { + if (wcfg.isChestProtected(BukkitAdapter.adapt(block.getLocation()))) { + event.blockList().clear(); + return; + } + } + } + + } + + /* + * Called on explosion prime + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onExplosionPrime(ExplosionPrimeEvent event) { + ConfigurationManager cfg = getConfig(); + Entity ent = event.getEntity(); + + if (cfg.activityHaltToggle) { + ent.remove(); + event.setCancelled(true); + return; + } + + BukkitWorldConfiguration wcfg = getWorldConfig(ent.getWorld()); + if (event.getEntityType() == EntityType.WITHER) { + if (wcfg.blockWitherExplosions) { + event.setCancelled(true); + return; + } + } else if (event.getEntityType() == EntityType.WITHER_SKULL) { + if (wcfg.blockWitherSkullExplosions) { + event.setCancelled(true); + return; + } + } else if (event.getEntityType() == EntityType.FIREBALL) { + if (wcfg.blockFireballExplosions) { + event.setCancelled(true); + return; + } + } else if (event.getEntityType() == EntityType.CREEPER) { + if (wcfg.blockCreeperExplosions) { + event.setCancelled(true); + return; + } + } else if (event.getEntityType() == EntityType.PRIMED_TNT + || event.getEntityType() == EntityType.MINECART_TNT) { + if (wcfg.blockTNTExplosions) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onCreatureSpawn(CreatureSpawnEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + event.setCancelled(true); + return; + } + + WorldConfiguration wcfg = getWorldConfig(event.getEntity().getWorld()); + + // allow spawning of creatures from plugins + if (!wcfg.blockPluginSpawning && event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.CUSTOM) { + return; + } + + // armor stands are living entities, but we check them as blocks/non-living entities, so ignore them here + if (Entities.isConsideredBuildingIfUsed(event.getEntity())) { + return; + } + + if (wcfg.allowTamedSpawns + && event.getEntity() instanceof Tameable // nullsafe check + && ((Tameable) event.getEntity()).isTamed()) { + return; + } + + EntityType entityType = event.getEntityType(); + + com.sk89q.worldedit.world.entity.EntityType weEntityType = BukkitAdapter.adapt(entityType); + + if (weEntityType != null && wcfg.blockCreatureSpawn.contains(weEntityType)) { + event.setCancelled(true); + return; + } + + Location eventLoc = event.getLocation(); + + if (wcfg.useRegions && cfg.useRegionsCreatureSpawnEvent) { + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(eventLoc)); + + if (!set.testState(null, Flags.MOB_SPAWNING)) { + event.setCancelled(true); + return; + } + + Set entityTypes = set.queryValue(null, Flags.DENY_SPAWN); + if (entityTypes != null && weEntityType != null && entityTypes.contains(weEntityType)) { + event.setCancelled(true); + return; + } + } + + if (wcfg.blockGroundSlimes && entityType == EntityType.SLIME + && eventLoc.getY() >= 60 + && event.getSpawnReason() == SpawnReason.NATURAL) { + event.setCancelled(true); + return; + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onCreatePortal(PortalCreateEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + if (wcfg.regionNetherPortalProtection + && event.getReason() == PortalCreateEvent.CreateReason.NETHER_PAIR + && !event.getBlocks().isEmpty()) { + final com.sk89q.worldedit.world.World world = BukkitAdapter.adapt(event.getWorld()); + final RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer() + .get(world); + if (regionManager == null) return; + LocalPlayer associable = null; + if (event.getEntity() instanceof Player) { + associable = getPlugin().wrapPlayer(((Player) event.getEntity())); + if (WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(associable, world)) { + return; + } + } + BlockVector3 min = null; + BlockVector3 max = null; + for (BlockState block : event.getBlocks()) { + BlockVector3 loc = BlockVector3.at(block.getX(), block.getY(), block.getZ()); + min = min == null ? loc : loc.getMinimum(min); + max = max == null ? loc : loc.getMaximum(max); + } + ProtectedCuboidRegion target = new ProtectedCuboidRegion("__portal_check", true, min, max); + final ApplicableRegionSet regions = regionManager.getApplicableRegions(target); + if (!regions.testState(associable, Flags.BUILD, Flags.BLOCK_PLACE)) { + if (associable != null) { + // NB there is no way to cancel the teleport without PTA (since PlayerPortal doesn't have block info) + // removing PTA was a mistake + associable.print("Destination is an a protected area."); + } + event.setCancelled(true); + } + } + + // NOTE: as of right now, bukkit doesn't fire this event for this (despite deprecating EntityCreatePortalEvent for it) + // maybe one day this code will be useful + if (event.getEntity() instanceof EnderDragon && wcfg.blockEnderDragonPortalCreation) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityTransform(EntityTransformEvent event) { + final Entity entity = event.getEntity(); + WorldConfiguration wcfg = getWorldConfig(entity.getWorld()); + + final EntityType type = entity.getType(); + if (wcfg.disableVillagerZap && type == EntityType.VILLAGER + && event.getTransformReason() == EntityTransformEvent.TransformReason.LIGHTNING) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPigZap(PigZapEvent event) { + final Entity entity = event.getEntity(); + WorldConfiguration wcfg = getWorldConfig(entity.getWorld()); + + if (wcfg.disablePigZap) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onCreeperPower(CreeperPowerEvent event) { + final Entity entity = event.getEntity(); + WorldConfiguration wcfg = getWorldConfig(entity.getWorld()); + + if (wcfg.disableCreeperPower) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityRegainHealth(EntityRegainHealthEvent event) { + RegainReason regainReason = event.getRegainReason(); + if (regainReason != RegainReason.REGEN && regainReason != RegainReason.SATIATED) { + return; + } + + Entity ent = event.getEntity(); + + WorldConfiguration wcfg = getWorldConfig(ent.getWorld()); + + if (wcfg.disableHealthRegain) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && ent instanceof Player + && !WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().testState( + BukkitAdapter.adapt(ent.getLocation()), + WorldGuardPlugin.inst().wrapPlayer((Player) ent), + Flags.HEALTH_REGEN)) { + event.setCancelled(true); + } + } + + @EventHandler(ignoreCancelled = true) + public void onFoodChange(FoodLevelChangeEvent event) { + if (event.getItem() != null) return; + HumanEntity ent = event.getEntity(); + if (!(ent instanceof Player)) return; + if (event.getFoodLevel() > ent.getFoodLevel()) return; + + LocalPlayer player = WorldGuardPlugin.inst().wrapPlayer((Player) ent); + WorldConfiguration wcfg = getWorldConfig(ent.getWorld()); + + if (wcfg.useRegions + && !WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().testState( + player.getLocation(), player, Flags.HUNGER_DRAIN)) { + event.setCancelled(true); + } + } + + /** + * Called when an entity changes a block somehow + * + * @param event Relevant event details + */ + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityChangeBlock(EntityChangeBlockEvent event) { + Entity ent = event.getEntity(); + + WorldConfiguration wcfg = getWorldConfig(ent.getWorld()); + if (ent instanceof FallingBlock) { + Material id = event.getBlock().getType(); + + if (id == Material.GRAVEL && wcfg.noPhysicsGravel) { + event.setCancelled(true); + return; + } + + if (id == Material.SAND && wcfg.noPhysicsSand) { + event.setCancelled(true); + return; + } + } else if (ent instanceof Enderman) { + if (wcfg.disableEndermanGriefing) { + event.setCancelled(true); + return; + } + } else if (ent.getType() == EntityType.WITHER) { + if (wcfg.blockWitherBlockDamage || wcfg.blockWitherExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions) { + Location location = event.getBlock().getLocation(); + if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(location), (RegionAssociable) null, Flags.WITHER_DAMAGE))) { + event.setCancelled(true); + return; + } + } + } else if (/*ent instanceof Zombie && */event instanceof EntityBreakDoorEvent) { + if (wcfg.blockZombieDoorDestruction) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onVehicleEnter(VehicleEnterEvent event) { + BukkitWorldConfiguration wcfg = getWorldConfig(event.getEntered().getWorld()); + + if (wcfg.blockEntityVehicleEntry && !(event.getEntered() instanceof Player)) { + event.setCancelled(true); + } + } + + /** + * Checks regions and config settings to protect items from being knocked + * out of item frames. + * @param attacker attacking entity + * @param defender item frame being damaged + * @return true if the event should be cancelled + */ + private boolean checkItemFrameProtection(Entity attacker, ItemFrame defender) { + World world = defender.getWorld(); + WorldConfiguration wcfg = getWorldConfig(world); + if (wcfg.useRegions) { + // bukkit throws this event when a player attempts to remove an item from a frame + if (!(attacker instanceof Player)) { + if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(defender.getLocation()), (RegionAssociable) null, Flags.ENTITY_ITEM_FRAME_DESTROY))) { + return true; + } + } + } + if (wcfg.blockEntityItemFrameDestroy && !(attacker instanceof Player)) { + return true; + } + return false; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java new file mode 100644 index 000000000..9d93a5191 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardHangingListener.java @@ -0,0 +1,107 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import org.bukkit.World; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Painting; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.hanging.HangingBreakEvent; +import org.bukkit.event.hanging.HangingBreakEvent.RemoveCause; +import org.bukkit.projectiles.ProjectileSource; + +/** + * Listener for painting related events. + */ +public class WorldGuardHangingListener extends AbstractListener { + + public WorldGuardHangingListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onHangingBreak(HangingBreakEvent event) { + Hanging hanging = event.getEntity(); + World world = hanging.getWorld(); + WorldConfiguration wcfg = getWorldConfig(world); + + if (event instanceof HangingBreakByEntityEvent) { + HangingBreakByEntityEvent entityEvent = (HangingBreakByEntityEvent) event; + Entity removerEntity = entityEvent.getRemover(); + if (removerEntity instanceof Projectile) { + Projectile projectile = (Projectile) removerEntity; + ProjectileSource remover = projectile.getShooter(); + removerEntity = (remover instanceof LivingEntity ? (LivingEntity) remover : null); + } + + if (!(removerEntity instanceof Player)) { + if (removerEntity instanceof Creeper) { + if (wcfg.blockCreeperBlockDamage || wcfg.blockCreeperExplosions) { + event.setCancelled(true); + return; + } + if (wcfg.useRegions && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(hanging.getLocation()), (RegionAssociable) null, Flags.CREEPER_EXPLOSION))) { + event.setCancelled(true); + return; + } + } + + // this now covers dispensers as well, if removerEntity is null above, + // due to a non-LivingEntity ProjectileSource + if (hanging instanceof Painting + && (wcfg.blockEntityPaintingDestroy + || (wcfg.useRegions + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(hanging.getLocation()), (RegionAssociable) null, Flags.ENTITY_PAINTING_DESTROY))))) { + event.setCancelled(true); + } else if (hanging instanceof ItemFrame + && (wcfg.blockEntityItemFrameDestroy + || (wcfg.useRegions + && !StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(hanging.getLocation()), (RegionAssociable) null, Flags.ENTITY_ITEM_FRAME_DESTROY))))) { + event.setCancelled(true); + } + } + } else { + // Explosions from mobs are not covered by HangingBreakByEntity + if (hanging instanceof Painting && wcfg.blockEntityPaintingDestroy + && event.getCause() == RemoveCause.EXPLOSION) { + event.setCancelled(true); + } else if (hanging instanceof ItemFrame && wcfg.blockEntityItemFrameDestroy + && event.getCause() == RemoveCause.EXPLOSION) { + event.setCancelled(true); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java new file mode 100644 index 000000000..6c722bccc --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardPlayerListener.java @@ -0,0 +1,438 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.player.ProcessPlayerEvent; +import com.sk89q.worldguard.bukkit.util.Events; +import com.sk89q.worldguard.bukkit.util.Materials; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.handler.GameModeFlag; +import com.sk89q.worldguard.util.Entities; +import com.sk89q.worldguard.util.command.CommandFilter; +import com.sk89q.worldguard.util.profile.Profile; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerGameModeChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.player.PlayerRespawnEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.ItemStack; + +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import javax.annotation.Nullable; + +/** + * Handles all events thrown in relation to a player. + */ +public class WorldGuardPlayerListener extends AbstractListener { + + private static final Logger log = Logger.getLogger(WorldGuardPlayerListener.class.getCanonicalName()); + private static final Pattern opPattern = Pattern.compile("^/(?:minecraft:)?(?:bukkit:)?(?:de)?op(?:\\s.*)?$", Pattern.CASE_INSENSITIVE); + + public WorldGuardPlayerListener(WorldGuardPlugin plugin) { + super(plugin); + } + + + @EventHandler + public void onPlayerGameModeChange(PlayerGameModeChangeEvent event) { + Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().getIfPresent(localPlayer); + if (session != null) { + GameModeFlag handler = session.getHandler(GameModeFlag.class); + if (handler != null && wcfg.useRegions && !WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, + localPlayer.getWorld())) { + GameMode expected = handler.getSetGameMode(); + if (handler.getOriginalGameMode() != null && expected != null && expected != BukkitAdapter.adapt(event.getNewGameMode())) { + log.info("Game mode change on " + player.getName() + " has been blocked due to the region GAMEMODE flag"); + event.setCancelled(true); + } + } + } + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + World world = player.getWorld(); + + ConfigurationManager cfg = getConfig(); + WorldConfiguration wcfg = getWorldConfig(world); + + if (cfg.activityHaltToggle) { + player.sendMessage(ChatColor.YELLOW + + "Intensive server activity has been HALTED."); + + int removed = 0; + + for (Entity entity : world.getEntities()) { + if (Entities.isIntensiveEntity(BukkitAdapter.adapt(entity))) { + entity.remove(); + removed++; + } + } + + if (removed > 10) { + log.info("Halt-Act: " + removed + " entities (>10) auto-removed from " + + player.getWorld()); + } + } + + if (wcfg.fireSpreadDisableToggle) { + player.sendMessage(ChatColor.YELLOW + + "Fire spread is currently globally disabled for this world."); + } + + Events.fire(new ProcessPlayerEvent(player)); + WorldGuard.getInstance().getExecutorService().submit(() -> + WorldGuard.getInstance().getProfileCache().put(new Profile(player.getUniqueId(), player.getName()))); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerChat(AsyncPlayerChatEvent event) { + Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + if (wcfg.useRegions) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + ApplicableRegionSet chatFrom = query.getApplicableRegions(localPlayer.getLocation()); + + if (!chatFrom.testState(localPlayer, Flags.SEND_CHAT)) { + String message = chatFrom.queryValue(localPlayer, Flags.DENY_MESSAGE); + RegionProtectionListener.formatAndSendDenyMessage("chat", localPlayer, message); + event.setCancelled(true); + return; + } + + boolean anyRemoved = false; + for (Iterator i = event.getRecipients().iterator(); i.hasNext();) { + Player rPlayer = i.next(); + LocalPlayer rLocal = getPlugin().wrapPlayer(rPlayer); + if (!query.testState(rLocal.getLocation(), rLocal, Flags.RECEIVE_CHAT)) { + i.remove(); + anyRemoved = true; + } + } + if (anyRemoved && event.getRecipients().isEmpty() && wcfg.regionCancelEmptyChatEvents) { + event.setCancelled(true); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerLogin(PlayerLoginEvent event) { + Player player = event.getPlayer(); + ConfigurationManager cfg = getConfig(); + + String hostKey = cfg.hostKeys.get(player.getUniqueId().toString()); + if (hostKey == null) { + hostKey = cfg.hostKeys.get(player.getName().toLowerCase()); + } + + if (hostKey != null) { + String hostname = event.getHostname(); + int colonIndex = hostname.indexOf(':'); + if (colonIndex != -1) { + hostname = hostname.substring(0, colonIndex); + } + + if (!hostname.equals(hostKey) + && !(cfg.hostKeysAllowFMLClients && + (hostname.equals(hostKey + "\u0000FML\u0000") || hostname.equals(hostKey + "\u0000FML2\u0000")))) { + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, + "You did not join with the valid host key!"); + log.warning("WorldGuard host key check: " + + player.getName() + " joined with '" + hostname + + "' but '" + hostKey + "' was expected. Kicked!"); + return; + } + } + + if (cfg.deopOnJoin) { + player.setOp(false); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onPlayerInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); + World world = player.getWorld(); + + if (event.getAction() == Action.RIGHT_CLICK_BLOCK) { + handleBlockRightClick(event); + } else if (event.getAction() == Action.PHYSICAL) { + handlePhysicalInteract(event); + } + + ConfigurationManager cfg = getConfig(); + WorldConfiguration wcfg = getWorldConfig(world); + + if (wcfg.removeInfiniteStacks + && !getPlugin().hasPermission(player, "worldguard.override.infinite-stack")) { + int slot = player.getInventory().getHeldItemSlot(); + ItemStack heldItem = player.getInventory().getItem(slot); + if (heldItem != null && heldItem.getAmount() < 0) { + player.getInventory().setItem(slot, null); + player.sendMessage(ChatColor.RED + "Infinite stack removed."); + } + } + } + + /** + * Called when a player right clicks a block. + * + * @param event Thrown event + */ + private void handleBlockRightClick(PlayerInteractEvent event) { + if (event.useItemInHand() == Event.Result.DENY) { + return; + } + + Block block = event.getClickedBlock(); + World world = block.getWorld(); + Material type = block.getType(); + Player player = event.getPlayer(); + @Nullable ItemStack item = event.getItem(); + + WorldConfiguration wcfg = getWorldConfig(world); + + // Infinite stack removal + if (Materials.isInventoryBlock(type) + && wcfg.removeInfiniteStacks + && !getPlugin().hasPermission(player, "worldguard.override.infinite-stack")) { + for (int slot = 0; slot < 40; slot++) { + ItemStack heldItem = player.getInventory().getItem(slot); + if (heldItem != null && heldItem.getAmount() < 0) { + player.getInventory().setItem(slot, null); + player.sendMessage(ChatColor.RED + "Infinite stack in slot #" + slot + " removed."); + } + } + } + + if (wcfg.useRegions) { + //Block placedIn = block.getRelative(event.getBlockFace()); + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(BukkitAdapter.adapt(block.getLocation())); + //ApplicableRegionSet placedInSet = plugin.getRegionContainer().createQuery().getApplicableRegions(placedIn.getLocation()); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + + if (item != null && item.getType().getKey().toString().equals(wcfg.regionWand) && getPlugin().hasPermission(player, "worldguard.region.wand")) { + if (set.size() > 0) { + player.sendMessage(ChatColor.YELLOW + "Can you build? " + (set.testState(localPlayer, Flags.BUILD) ? "Yes" : "No")); + + StringBuilder str = new StringBuilder(); + for (Iterator it = set.iterator(); it.hasNext();) { + str.append(it.next().getId()); + if (it.hasNext()) { + str.append(", "); + } + } + + localPlayer.print("Applicable regions: " + str); + } else { + localPlayer.print("WorldGuard: No defined regions here!"); + } + + event.setUseItemInHand(Event.Result.DENY); + } + } + } + + /** + * Called when a player steps on a pressure plate or tramples crops. + * + * @param event Thrown event + */ + private void handlePhysicalInteract(PlayerInteractEvent event) { + if (event.useInteractedBlock() == Event.Result.DENY) return; + + Player player = event.getPlayer(); + Block block = event.getClickedBlock(); //not actually clicked but whatever + Material type = block.getType(); + World world = player.getWorld(); + + WorldConfiguration wcfg = getWorldConfig(world); + + if (type == Material.FARMLAND && wcfg.disablePlayerCropTrampling) { + event.setCancelled(true); + return; + } + if (type == Material.TURTLE_EGG && wcfg.disablePlayerTurtleEggTrampling) { + event.setCancelled(true); + return; + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onPlayerRespawn(PlayerRespawnEvent event) { + Player player = event.getPlayer(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (wcfg.useRegions) { + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()); + + com.sk89q.worldedit.util.Location spawn = set.queryValue(localPlayer, Flags.SPAWN_LOC); + + if (spawn != null) { + event.setRespawnLocation(BukkitAdapter.adapt(spawn)); + } + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onItemHeldChange(PlayerItemHeldEvent event) { + Player player = event.getPlayer(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (wcfg.removeInfiniteStacks + && !getPlugin().hasPermission(player, "worldguard.override.infinite-stack")) { + int newSlot = event.getNewSlot(); + ItemStack heldItem = player.getInventory().getItem(newSlot); + if (heldItem != null && heldItem.getAmount() < 0) { + player.getInventory().setItem(newSlot, null); + player.sendMessage(ChatColor.RED + "Infinite stack removed."); + } + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + ConfigurationManager cfg = getConfig(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (wcfg.useRegions && cfg.usePlayerTeleports) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + ApplicableRegionSet set = query.getApplicableRegions(BukkitAdapter.adapt(event.getTo())); + ApplicableRegionSet setFrom = query.getApplicableRegions(BukkitAdapter.adapt(event.getFrom())); + + if (event.getCause() == TeleportCause.ENDER_PEARL) { + if (!WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, localPlayer.getWorld())) { + boolean cancel = false; + String message = null; + if (!setFrom.testState(localPlayer, Flags.ENDERPEARL)) { + cancel = true; + message = setFrom.queryValue(localPlayer, Flags.EXIT_DENY_MESSAGE); + } else if (!set.testState(localPlayer, Flags.ENDERPEARL)) { + cancel = true; + message = set.queryValue(localPlayer, Flags.ENTRY_DENY_MESSAGE); + } + if (cancel) { + if (message != null && !message.isEmpty()) { + player.sendMessage(message); + } + event.setCancelled(true); + return; + } + } + } else if (event.getCause() == TeleportCause.CHORUS_FRUIT) { + if (!WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, localPlayer.getWorld())) { + boolean cancel = false; + String message = null; + if (!setFrom.testState(localPlayer, Flags.CHORUS_TELEPORT)) { + cancel = true; + message = setFrom.queryValue(localPlayer, Flags.EXIT_DENY_MESSAGE); + } else if (!set.testState(localPlayer, Flags.CHORUS_TELEPORT)) { + cancel = true; + message = set.queryValue(localPlayer, Flags.ENTRY_DENY_MESSAGE); + } + if (cancel) { + if (message != null && !message.isEmpty()) { + player.sendMessage(message); + } + event.setCancelled(true); + return; + } + } + } + if (null != WorldGuard.getInstance().getPlatform().getSessionManager().get(localPlayer) + .testMoveTo(localPlayer, BukkitAdapter.adapt(event.getTo()), MoveType.TELEPORT)) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + ConfigurationManager cfg = getConfig(); + WorldConfiguration wcfg = getWorldConfig(player.getWorld()); + + if (wcfg.useRegions && !WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(localPlayer, localPlayer.getWorld())) { + ApplicableRegionSet set = + WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().getApplicableRegions(localPlayer.getLocation()); + + Set allowedCommands = set.queryValue(localPlayer, Flags.ALLOWED_CMDS); + Set blockedCommands = set.queryValue(localPlayer, Flags.BLOCKED_CMDS); + CommandFilter test = new CommandFilter(allowedCommands, blockedCommands); + + if (!test.apply(event.getMessage())) { + String message = set.queryValue(localPlayer, Flags.DENY_MESSAGE); + RegionProtectionListener.formatAndSendDenyMessage("use " + event.getMessage(), localPlayer, message); + event.setCancelled(true); + return; + } + } + + if (cfg.blockInGameOp) { + if (opPattern.matcher(event.getMessage()).matches()) { + player.sendMessage(ChatColor.RED + "/op and /deop can only be used in console (as set by a WG setting)."); + event.setCancelled(true); + return; + } + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardServerListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardServerListener.java new file mode 100644 index 000000000..27923286b --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardServerListener.java @@ -0,0 +1,46 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.server.PluginEnableEvent; + +public class WorldGuardServerListener extends AbstractListener { + + public WorldGuardServerListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler + public void onPluginEnable(PluginEnableEvent event) { + if (event.getPlugin().getDescription().getName().equalsIgnoreCase("CommandBook")) { + getConfig().updateCommandBookGodMode(); + } + } + + @EventHandler + public void onPluginDisable(PluginDisableEvent event) { + if (event.getPlugin().getDescription().getName().equalsIgnoreCase("CommandBook")) { + getConfig().updateCommandBookGodMode(); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardVehicleListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardVehicleListener.java new file mode 100644 index 000000000..4c044808e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardVehicleListener.java @@ -0,0 +1,78 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.util.Locations; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.EventHandler; +import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.util.Vector; + +import java.util.List; +import java.util.stream.Collectors; + +public class WorldGuardVehicleListener extends AbstractListener { + + public WorldGuardVehicleListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler + public void onVehicleMove(VehicleMoveEvent event) { + Vehicle vehicle = event.getVehicle(); + if (vehicle.getPassengers().isEmpty()) return; + List playerPassengers = vehicle.getPassengers().stream() + .filter(ent -> ent instanceof Player).map(ent -> (Player) ent).collect(Collectors.toList()); + if (playerPassengers.isEmpty()) { + return; + } + World world = vehicle.getWorld(); + WorldConfiguration wcfg = getWorldConfig(world); + + if (wcfg.useRegions) { + // Did we move a block? + if (Locations.isDifferentBlock(BukkitAdapter.adapt(event.getFrom()), BukkitAdapter.adapt(event.getTo()))) { + for (Player player : playerPassengers) { + LocalPlayer localPlayer = getPlugin().wrapPlayer(player); + Location lastValid; + if ((lastValid = WorldGuard.getInstance().getPlatform().getSessionManager().get(localPlayer) + .testMoveTo(localPlayer, BukkitAdapter.adapt(event.getTo()), MoveType.RIDE)) != null) { + vehicle.setVelocity(new Vector(0, 0, 0)); + vehicle.teleport(event.getFrom()); + if (Locations.isDifferentBlock(lastValid, BukkitAdapter.adapt(event.getFrom()))) { + Vector dir = player.getLocation().getDirection(); + player.teleport(BukkitAdapter.adapt(lastValid).setDirection(dir)); + } + return; + } + } + } + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWeatherListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWeatherListener.java new file mode 100644 index 000000000..7edd44420 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWeatherListener.java @@ -0,0 +1,97 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.weather.LightningStrikeEvent; +import org.bukkit.event.weather.ThunderChangeEvent; +import org.bukkit.event.weather.WeatherChangeEvent; + +public class WorldGuardWeatherListener extends AbstractListener { + + public WorldGuardWeatherListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onWeatherChange(WeatherChangeEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + if (event.toWeatherState()) { + if (wcfg.disableWeather) { + event.setCancelled(true); + } + } else { + if (!wcfg.disableWeather && wcfg.alwaysRaining) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onThunderChange(ThunderChangeEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + if (event.toThunderState()) { + if (wcfg.disableThunder) { + event.setCancelled(true); + } + } else { + if (!wcfg.disableWeather && wcfg.alwaysThundering) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onLightningStrike(LightningStrikeEvent event) { + WorldConfiguration wcfg = getWorldConfig(event.getWorld()); + + if (!wcfg.disallowedLightningBlocks.isEmpty()) { + final Block target = event.getLightning().getLocation().getBlock(); + Material targetId = target.getType(); + if (targetId == Material.AIR) { + targetId = target.getRelative(BlockFace.DOWN).getType(); + } + if (wcfg.disallowedLightningBlocks.contains(BukkitAdapter.asBlockType(targetId).getId())) { + event.setCancelled(true); + } + } + + Location loc = event.getLightning().getLocation(); + if (wcfg.useRegions) { + if (!StateFlag.test(WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery().queryState(BukkitAdapter.adapt(loc), (RegionAssociable) null, Flags.LIGHTNING))) { + event.setCancelled(true); + } + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWorldListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWorldListener.java new file mode 100644 index 000000000..643c988c7 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldGuardWorldListener.java @@ -0,0 +1,91 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.util.Entities; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.ChunkLoadEvent; +import org.bukkit.event.world.WorldLoadEvent; + +import java.util.logging.Logger; + +public class WorldGuardWorldListener extends AbstractListener { + + private static final Logger log = Logger.getLogger(WorldGuardWorldListener.class.getCanonicalName()); + + public WorldGuardWorldListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler + public void onChunkLoad(ChunkLoadEvent event) { + ConfigurationManager cfg = getConfig(); + + if (cfg.activityHaltToggle) { + int removed = 0; + + for (Entity entity : event.getChunk().getEntities()) { + if (Entities.isIntensiveEntity(BukkitAdapter.adapt(entity))) { + entity.remove(); + removed++; + } + } + + if (removed > 50) { + log.info("Halt-Act: " + removed + " entities (>50) auto-removed from " + event.getChunk().toString()); + } + } + } + + @EventHandler + public void onWorldLoad(WorldLoadEvent event) { + initWorld(event.getWorld()); + } + + /** + * Initialize the settings for the specified world + * @see WorldConfiguration#alwaysRaining + * @see WorldConfiguration#disableWeather + * @see WorldConfiguration#alwaysThundering + * @see WorldConfiguration#disableThunder + * @param world The specified world + */ + public void initWorld(World world) { + WorldConfiguration wcfg = getWorldConfig(world); + if (wcfg.alwaysRaining && !wcfg.disableWeather) { + world.setStorm(true); + } else if (wcfg.disableWeather && !wcfg.alwaysRaining) { + world.setStorm(false); + } + if (wcfg.alwaysThundering && !wcfg.disableThunder) { + world.setThundering(true); + } else if (wcfg.disableThunder && !wcfg.alwaysThundering) { + world.setStorm(false); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldRulesListener.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldRulesListener.java new file mode 100644 index 000000000..598266568 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/WorldRulesListener.java @@ -0,0 +1,63 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener; + +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.entity.SpawnEntityEvent; +import com.sk89q.worldguard.config.WorldConfiguration; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +public class WorldRulesListener extends AbstractListener { + + /** + * Construct the listener. + * + * @param plugin an instance of WorldGuardPlugin + */ + public WorldRulesListener(WorldGuardPlugin plugin) { + super(plugin); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onSpawnEntity(final SpawnEntityEvent event) { + if (event.getEffectiveType() == EntityType.EXPERIENCE_ORB) { + WorldConfiguration config = getWorldConfig(event.getWorld()); + + if (config.disableExpDrops) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onPotionEffect(EntityPotionEffectEvent event) { + if (event.getCause() == EntityPotionEffectEvent.Cause.CONDUIT) { + WorldConfiguration config = getWorldConfig(event.getEntity().getWorld()); + + if (config.disableConduitEffects) { + event.setCancelled(true); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonExtendKey.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonExtendKey.java new file mode 100644 index 000000000..21589c68f --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonExtendKey.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce; + +import org.bukkit.block.Block; +import org.bukkit.event.block.BlockPistonExtendEvent; + +import java.util.List; + +public class BlockPistonExtendKey { + + private final Block piston; + private final List blocks; + private final int blocksHashCode; + + public BlockPistonExtendKey(BlockPistonExtendEvent event) { + piston = event.getBlock(); + blocks = event.getBlocks(); + blocksHashCode = blocks.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlockPistonExtendKey that = (BlockPistonExtendKey) o; + + if (!blocks.equals(that.blocks)) return false; + if (!piston.equals(that.piston)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = piston.hashCode(); + result = 31 * result + blocksHashCode; + return result; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonRetractKey.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonRetractKey.java new file mode 100644 index 000000000..459d3a1a7 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/BlockPistonRetractKey.java @@ -0,0 +1,55 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce; + +import org.bukkit.block.Block; +import org.bukkit.event.block.BlockPistonRetractEvent; + +public class BlockPistonRetractKey { + + private final Block piston; + private final Block retract; + + public BlockPistonRetractKey(BlockPistonRetractEvent event) { + piston = event.getBlock(); + retract = event.getRetractLocation().getBlock(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlockPistonRetractKey that = (BlockPistonRetractKey) o; + + if (!piston.equals(that.piston)) return false; + if (!retract.equals(that.retract)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = piston.hashCode(); + result = 31 * result + retract.hashCode(); + return result; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/EventDebounce.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/EventDebounce.java new file mode 100644 index 000000000..1675e4b78 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/EventDebounce.java @@ -0,0 +1,89 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce; + +import com.sk89q.worldguard.bukkit.util.Events; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +public class EventDebounce { + + private final LoadingCache cache; + + public EventDebounce(int debounceTime) { + cache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(debounceTime, TimeUnit.MILLISECONDS) + .concurrencyLevel(2) + .build(new CacheLoader() { + @Override + public Entry load(K key) throws Exception { + return new Entry(); + } + }); + } + + public void fireToCancel(Cancellable originalEvent, T firedEvent, K key) { + Entry entry = cache.getUnchecked(key); + if (entry.cancelled != null) { + if (entry.cancelled) { + originalEvent.setCancelled(true); + } + } else { + boolean cancelled = Events.fireAndTestCancel(firedEvent); + if (cancelled) { + originalEvent.setCancelled(true); + } + entry.cancelled = cancelled; + } + } + + @Nullable + public Entry getIfNotPresent(K key, Cancellable originalEvent) { + Entry entry = cache.getUnchecked(key); + if (entry.cancelled != null) { + if (entry.cancelled) { + originalEvent.setCancelled(true); + } + return null; + } else { + return entry; + } + } + + public static EventDebounce create(int debounceTime) { + return new EventDebounce<>(debounceTime); + } + + public static class Entry { + private Boolean cancelled; + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/AbstractEventDebounce.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/AbstractEventDebounce.java new file mode 100644 index 000000000..b7ec0d8d2 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/AbstractEventDebounce.java @@ -0,0 +1,85 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce.legacy; + +import com.sk89q.worldguard.bukkit.util.Events; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeUnit; + +public class AbstractEventDebounce { + + private final LoadingCache cache; + + AbstractEventDebounce(int debounceTime) { + cache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(debounceTime, TimeUnit.MILLISECONDS) + .concurrencyLevel(2) + .build(new CacheLoader() { + @Override + public Entry load(K key) throws Exception { + return new Entry(); + } + }); + } + + protected void debounce(K key, Cancellable originalEvent, T firedEvent) { + Entry entry = cache.getUnchecked(key); + if (entry.cancelled != null) { + if (entry.cancelled) { + originalEvent.setCancelled(true); + } + } else { + boolean cancelled = Events.fireAndTestCancel(firedEvent); + if (cancelled) { + originalEvent.setCancelled(true); + } + entry.cancelled = cancelled; + } + } + + @Nullable + protected Entry getEntry(K key, Cancellable originalEvent) { + Entry entry = cache.getUnchecked(key); + if (entry.cancelled != null) { + if (entry.cancelled) { + originalEvent.setCancelled(true); + } + return null; + } else { + return entry; + } + } + + public static class Entry { + private Boolean cancelled; + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/BlockEntityEventDebounce.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/BlockEntityEventDebounce.java new file mode 100644 index 000000000..21aad4312 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/BlockEntityEventDebounce.java @@ -0,0 +1,73 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce.legacy; + +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.BlockEntityEventDebounce.Key; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +public class BlockEntityEventDebounce extends AbstractEventDebounce { + + public BlockEntityEventDebounce(int debounceTime) { + super(debounceTime); + } + + public void debounce(Block block, Entity entity, Cancellable originalEvent, T firedEvent) { + super.debounce(new Key(block, entity), originalEvent, firedEvent); + } + + protected static class Key { + private final Block block; + private final Material blockMaterial; + private final Entity entity; + + private Key(Block block, Entity entity) { + this.block = block; + this.blockMaterial = block.getType(); + this.entity = entity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (!block.equals(key.block)) return false; + if (blockMaterial != key.blockMaterial) return false; + if (!entity.equals(key.entity)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = block.hashCode(); + result = 31 * result + blockMaterial.hashCode(); + result = 31 * result + entity.hashCode(); + return result; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/EntityEntityEventDebounce.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/EntityEntityEventDebounce.java new file mode 100644 index 000000000..dd7417f71 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/EntityEntityEventDebounce.java @@ -0,0 +1,67 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce.legacy; + +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.EntityEntityEventDebounce.Key; +import org.bukkit.entity.Entity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; + +public class EntityEntityEventDebounce extends AbstractEventDebounce { + + public EntityEntityEventDebounce(int debounceTime) { + super(debounceTime); + } + + public void debounce(Entity source, Entity target, Cancellable originalEvent, T firedEvent) { + super.debounce(new Key(source, target), originalEvent, firedEvent); + } + + protected static class Key { + private final Entity source; + private final Entity target; + + public Key(Entity source, Entity target) { + this.source = source; + this.target = target; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (!source.equals(key.source)) return false; + if (!target.equals(key.target)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = source.hashCode(); + result = 31 * result + target.hashCode(); + return result; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/InventoryMoveItemEventDebounce.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/InventoryMoveItemEventDebounce.java new file mode 100644 index 000000000..bbd39867c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/listener/debounce/legacy/InventoryMoveItemEventDebounce.java @@ -0,0 +1,123 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.listener.debounce.legacy; + +import com.sk89q.worldguard.bukkit.listener.debounce.legacy.InventoryMoveItemEventDebounce.Key; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Chest; +import org.bukkit.block.DoubleChest; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.inventory.InventoryHolder; + +public class InventoryMoveItemEventDebounce extends AbstractEventDebounce { + + public InventoryMoveItemEventDebounce(int debounceTime) { + super(debounceTime); + } + + public Entry tryDebounce(InventoryMoveItemEvent event) { + return super.getEntry(new Key(event), event); + } + + protected static class Key { + private final Object cause; + private final Object source; + private final Object target; + + public Key(InventoryMoveItemEvent event) { + cause = transform(event.getInitiator().getHolder()); + source = transform(event.getSource().getHolder()); + target = transform(event.getDestination().getHolder()); + } + + private Object transform(InventoryHolder holder) { + if (holder instanceof BlockState) { + return new BlockMaterialKey(((BlockState) holder).getBlock()); + } else if (holder instanceof DoubleChest) { + InventoryHolder left = ((DoubleChest) holder).getLeftSide(); + if (left instanceof Chest) { + return new BlockMaterialKey(((Chest) left).getBlock()); + } else { + return holder; + } + } else { + return holder; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (cause != null ? !cause.equals(key.cause) : key.cause != null) + return false; + if (source != null ? !source.equals(key.source) : key.source != null) + return false; + if (target != null ? !target.equals(key.target) : key.target != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = cause != null ? cause.hashCode() : 0; + result = 31 * result + (source != null ? source.hashCode() : 0); + result = 31 * result + (target != null ? target.hashCode() : 0); + return result; + } + } + + private static class BlockMaterialKey { + private final Block block; + private final Material material; + + private BlockMaterialKey(Block block) { + this.block = block; + material = block.getType(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlockMaterialKey that = (BlockMaterialKey) o; + + if (!block.equals(that.block)) return false; + if (material != that.material) return false; + + return true; + } + + @Override + public int hashCode() { + int result = block.hashCode(); + result = 31 * result + material.hashCode(); + return result; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/DisallowedPVPEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/DisallowedPVPEvent.java new file mode 100644 index 000000000..312a19bb8 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/DisallowedPVPEvent.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.protection.events; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +/** + * This event is fired when PVP is disallowed between players due to a "pvp deny" flag. + * Cancelling this event allows the PVP in spite of this. + * + * @author Score_Under + */ +public class DisallowedPVPEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private boolean cancelled = false; + private final Player attacker; + private final Player defender; + private final Event event; + + public DisallowedPVPEvent(final Player attacker, final Player defender, Event event) { + this.attacker = attacker; + this.defender = defender; + this.event = event; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * @return the attacking player. + */ + public Player getAttacker() { + return attacker; + } + + /** + * @return the defending player. + */ + public Player getDefender() { + return defender; + } + + public Event getCause() { + return event; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/flags/FlagContextCreateEvent.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/flags/FlagContextCreateEvent.java new file mode 100644 index 000000000..25ca01640 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/protection/events/flags/FlagContextCreateEvent.java @@ -0,0 +1,56 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.protection.events.flags; + +import com.sk89q.worldguard.protection.flags.FlagContext.FlagContextBuilder; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class FlagContextCreateEvent extends Event { + + private FlagContextBuilder builder; + + public FlagContextCreateEvent(FlagContextBuilder builder) { + this.builder = builder; + } + + /** + * Add an object to the flag context with the given key. Keys must be unique. + * + * @param key a unique string to identify the object + * @param value the object to store in the context + * @return true if added successfully, false if the key was already used + */ + public boolean addObject(String key, Object value) { + return builder.tryAddToMap(key, value); + } + + private static final HandlerList handlers = new HandlerList(); + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/BukkitSessionManager.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/BukkitSessionManager.java new file mode 100644 index 000000000..8c00f715a --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/BukkitSessionManager.java @@ -0,0 +1,99 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.session; + +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.bukkit.BukkitPlayer; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.bukkit.event.player.ProcessPlayerEvent; +import com.sk89q.worldguard.session.AbstractSessionManager; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.handler.Handler; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import java.util.Collection; + +/** + * Keeps tracks of sessions and also does session-related handling + * (flags, etc.). + */ +public class BukkitSessionManager extends AbstractSessionManager implements Runnable, Listener { + + private boolean useTimings; + + @Override + protected Handler.Factory wrapForRegistration(Handler.Factory factory) { + return useTimings ? new TimedHandlerFactory(factory) : factory; + } + + /** + * Re-initialize handlers and clear "last position," "last state," etc. + * information for all players. + */ + @Override + public void resetAllStates() { + Collection players = Bukkit.getServer().getOnlinePlayers(); + for (Player player : players) { + BukkitPlayer bukkitPlayer = new BukkitPlayer(WorldGuardPlugin.inst(), player); + Session session = getIfPresent(bukkitPlayer); + if (session != null) { + session.resetState(bukkitPlayer); + } + } + } + + @EventHandler + public void onPlayerProcess(ProcessPlayerEvent event) { + // Pre-load a session + LocalPlayer player = WorldGuardPlugin.inst().wrapPlayer(event.getPlayer()); + get(player).initialize(player); + } + + @Override + public void run() { + for (Player player : Bukkit.getServer().getOnlinePlayers()) { + LocalPlayer localPlayer = WorldGuardPlugin.inst().wrapPlayer(player); + get(localPlayer).tick(localPlayer); + } + } + + @Override + public boolean hasBypass(LocalPlayer player, World world) { + if (player instanceof BukkitPlayer) { + if (((BukkitPlayer) player).getPlayer().hasMetadata("NPC") + && WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world).fakePlayerBuildOverride) + return true; + } + return super.hasBypass(player, world); + } + + public boolean isUsingTimings() { + return useTimings; + } + + public void setUsingTimings(boolean useTimings) { + this.useTimings = useTimings; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/TimedHandlerFactory.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/TimedHandlerFactory.java new file mode 100644 index 000000000..4db5118c4 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/session/TimedHandlerFactory.java @@ -0,0 +1,132 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.session; + +import co.aikar.timings.lib.MCTiming; +import co.aikar.timings.lib.TimingManager; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.bukkit.WorldGuardPlugin; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.handler.Handler; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + + +import javax.annotation.Nullable; +import java.security.CodeSource; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +class TimedHandlerFactory extends Handler.Factory { + + private static final TimingManager TIMINGS = TimingManager.of(WorldGuardPlugin.inst()); + private static final MCTiming UNKNOWN_SOURCE = TIMINGS.of("Third-Party Session Handlers"); + private static final Map PLUGIN_SOURCES = new HashMap<>(); + + private final Handler.Factory factory; + private final MCTiming timing; + + TimedHandlerFactory(Handler.Factory factory) { + this.factory = factory; + this.timing = makeTiming(); + } + + private MCTiming makeTiming() { + CodeSource codeSource = factory.getClass().getProtectionDomain().getCodeSource(); + TimingManager owner = PLUGIN_SOURCES.computeIfAbsent(codeSource, source -> { + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { + CodeSource pluginSource = plugin.getClass().getProtectionDomain().getCodeSource(); + if (Objects.equals(pluginSource, source)) { + return TimingManager.of(plugin); + } + } + return null; + }); + String handlerName = factory.getClass().getEnclosingClass().getSimpleName(); + return owner == null + ? TIMINGS.of(handlerName, UNKNOWN_SOURCE) + : owner.of(handlerName, owner.of("Session Handlers")); + } + + @Override + public Handler create(Session session) { + return new TimedHandler(factory.create(session), session, timing); + } + + static class TimedHandler extends Handler { + private final Handler handler; + private final MCTiming timing; + + TimedHandler(Handler innerHandler, Session session, MCTiming timing) { + super(session); + this.handler = innerHandler; + this.timing = timing; + } + + @Override + public void initialize(LocalPlayer player, Location current, ApplicableRegionSet set) { + try (MCTiming ignored = timing.startTiming()) { + handler.initialize(player, current, set); + } + } + + @Override + public boolean testMoveTo(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, MoveType moveType) { + try (MCTiming ignored = timing.startTiming()) { + return handler.testMoveTo(player, from, to, toSet, moveType); + } + } + + @Override + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Set entered, Set exited, MoveType moveType) { + try (MCTiming ignored = timing.startTiming()) { + return handler.onCrossBoundary(player, from, to, toSet, entered, exited, moveType); + } + } + + @Override + public void tick(LocalPlayer player, ApplicableRegionSet set) { + try (MCTiming ignored = timing.startTiming()) { + handler.tick(player, set); + } + } + + @Nullable + @Override + public StateFlag.State getInvincibility(LocalPlayer player) { + try (MCTiming ignored = timing.startTiming()) { + return handler.getInvincibility(player); + } + } + + @Override + public Handler getWrappedHandler() { + return handler; + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Blocks.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Blocks.java new file mode 100644 index 000000000..37da4f49a --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Blocks.java @@ -0,0 +1,69 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import io.papermc.lib.PaperLib; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.Chest; +import org.bukkit.util.Vector; + +import java.util.Collections; +import java.util.List; + +/** + * Utility methods to deal with blocks. + */ +public final class Blocks { + + private Blocks() { + } + + /** + * Get a list of connected blocks to the given block, not including + * the given block. + * + * @param block the block + * @return a list of connected blocks, not including the given block + */ + public static List getConnected(Block block) { + BlockState state = PaperLib.getBlockState(block, false).getState(); + BlockData data = state.getBlockData(); + + if (data instanceof Bed) { + Bed bed = (Bed) data; + return Collections.singletonList(block.getRelative(bed.getPart() == Bed.Part.FOOT + ? bed.getFacing() : bed.getFacing().getOppositeFace())); + } else if (data instanceof Chest) { + final Chest chest = (Chest) data; + Chest.Type type = chest.getType(); + if (type == Chest.Type.SINGLE) { + return Collections.emptyList(); + } + Vector offset = chest.getFacing().getDirection().rotateAroundY(Math.PI / 2 * (type == Chest.Type.LEFT ? -1 : 1)); + return Collections.singletonList(block.getRelative((int) Math.round(offset.getX()), 0, (int) Math.round(offset.getZ()))); + } else { + return Collections.emptyList(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Entities.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Entities.java new file mode 100644 index 000000000..ef3515ecb --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Entities.java @@ -0,0 +1,221 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import org.bukkit.entity.Ambient; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Creature; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Flying; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Minecart; +import org.bukkit.entity.Monster; +import org.bukkit.entity.NPC; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.Shulker; +import org.bukkit.entity.Slime; +import org.bukkit.entity.SpectralArrow; +import org.bukkit.entity.Steerable; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.Tameable; +import org.bukkit.entity.Vehicle; +import org.bukkit.entity.minecart.ExplosiveMinecart; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.projectiles.ProjectileSource; + +import javax.annotation.Nullable; + +public final class Entities { + + private Entities() { + } + + /** + * Test whether the given entity is tameable and tamed. + * + * @param entity the entity, or null + * @return true if tamed + */ + public static boolean isTamed(@Nullable Entity entity) { + return entity instanceof Tameable && ((Tameable) entity).isTamed(); + } + + /** + * Return if the given entity type is TNT-based. + * + * @param entity the entity + * @return true if TNT based + */ + public static boolean isTNTBased(Entity entity) { + return entity instanceof TNTPrimed || entity instanceof ExplosiveMinecart; + } + + /** + * Return if the given entity type is a fireball + * (not including wither skulls). + * + * @param type the type + * @return true if a fireball + */ + public static boolean isFireball(EntityType type) { + return type == EntityType.FIREBALL || type == EntityType.SMALL_FIREBALL; + } + + /** + * Test whether the given entity can be ridden if it is right clicked. + * + * @param entity the entity + * @return true if the entity can be ridden + */ + public static boolean isRiddenOnUse(Entity entity) { + return entity instanceof Steerable ? ((Steerable) entity).hasSaddle() : entity instanceof Vehicle; + } + + /** + * Test whether the given entity type is a vehicle type. + * + * @param type the type + * @return true if the type is a vehicle type + */ + public static boolean isVehicle(EntityType type) { + return type == EntityType.BOAT + || isMinecart(type); + } + + /** + * Test whether the given entity type is a Minecart type. + * + * @param type the type + * @return true if the type is a Minecart type + */ + public static boolean isMinecart(EntityType type) { + return type == EntityType.MINECART + || type == EntityType.MINECART_CHEST + || type == EntityType.MINECART_COMMAND + || type == EntityType.MINECART_FURNACE + || type == EntityType.MINECART_HOPPER + || type == EntityType.MINECART_MOB_SPAWNER + || type == EntityType.MINECART_TNT; + } + + /** + * Get the underlying shooter of a projectile if one exists. + * + * @param entity the entity + * @return the shooter + */ + public static Entity getShooter(Entity entity) { + + while (entity instanceof Projectile) { + Projectile projectile = (Projectile) entity; + ProjectileSource remover = projectile.getShooter(); + if (remover instanceof Entity && remover != entity) { + entity = (Entity) remover; + } else { + return entity; + } + } + + return entity; + } + + /** + * Test whether an entity is hostile. + * + * @param entity the entity + * @return true if hostile + */ + public static boolean isHostile(Entity entity) { + return entity instanceof Monster + || entity instanceof Slime + || entity instanceof Flying + || entity instanceof EnderDragon + || entity instanceof Shulker; + } + + /** + * Test whether an entity is a non-hostile creature. + * + * @param entity + * @return true if non-hostile + */ + public static boolean isNonHostile(Entity entity) { + return !isHostile(entity) && entity instanceof Creature; + } + + /** + * Test whether an entity is ambient. + * + * @param entity the entity + * @return true if ambient + */ + public static boolean isAmbient(Entity entity) { + return entity instanceof Ambient; + } + + /** + * Test whether an entity is an NPC. + * + * @param entity the entity + * @return true if an NPC + */ + public static boolean isNPC(Entity entity) { + return entity instanceof NPC || entity.hasMetadata("NPC"); + } + + /** + * Test whether an entity is a creature (a living thing) that is + * not a player. + * + * @param entity the entity + * @return true if a non-player creature + */ + public static boolean isNonPlayerCreature(Entity entity) { + return entity instanceof LivingEntity && !(entity instanceof Player); + } + + /** + * Test whether using the given entity should be considered "building" + * rather than merely using an entity. + * + * @param entity the entity + * @return true if considered building + */ + public static boolean isConsideredBuildingIfUsed(Entity entity) { + return entity instanceof Hanging + || entity instanceof ArmorStand + || entity instanceof EnderCrystal + || entity instanceof Minecart && entity instanceof InventoryHolder; + } + + public static boolean isPotionArrow(Entity entity) { + return entity instanceof Arrow || entity instanceof SpectralArrow; + } + + public static boolean isAoECloud(EntityType type) { + return type == EntityType.AREA_EFFECT_CLOUD; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Events.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Events.java new file mode 100644 index 000000000..a42d75a5c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Events.java @@ -0,0 +1,163 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.sk89q.worldguard.bukkit.event.BulkEvent; +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Result; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.player.PlayerInteractEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility methods to deal with events. + */ +public final class Events { + + private Events() { + } + + /** + * Fire an event. + * + * @param event the event + */ + public static void fire(Event event) { + checkNotNull(event); + Bukkit.getServer().getPluginManager().callEvent(event); + } + + /** + * Fire the {@code eventToFire} and return whether the event was cancelled. + * + * @param eventToFire the event to fire + * @param an event that can be fired and is cancellable + * @return true if the event was cancelled + */ + public static boolean fireAndTestCancel(T eventToFire) { + Bukkit.getServer().getPluginManager().callEvent(eventToFire); + return eventToFire.isCancelled(); + } + + /** + * Fire the {@code eventToFire} and cancel the original if the fired event + * is cancelled. + * + * @param original the original event to potentially cancel + * @param eventToFire the event to fire to consider cancelling the original event + * @param an event that can be fired and is cancellable + * @return true if the event was fired and it caused the original event to be cancelled + */ + public static boolean fireToCancel(Cancellable original, T eventToFire) { + Bukkit.getServer().getPluginManager().callEvent(eventToFire); + if (eventToFire.isCancelled()) { + original.setCancelled(true); + return true; + } + + return false; + } + + /** + * Fire the {@code eventToFire} and cancel the original if the fired event + * is cancelled. + * + * @param original the original event to potentially cancel + * @param eventToFire the event to fire to consider cancelling the original event + * @param an event that can be fired and is cancellable + * @return true if the event was fired and it caused the original event to be cancelled + */ + public static boolean fireItemEventToCancel(PlayerInteractEvent original, T eventToFire) { + Bukkit.getServer().getPluginManager().callEvent(eventToFire); + if (eventToFire.isCancelled()) { + original.setUseItemInHand(Result.DENY); + return true; + } + + return false; + } + + /** + * Fire the {@code eventToFire} and cancel the original if the fired event + * is explicitly cancelled. + * + * @param original the original event to potentially cancel + * @param eventToFire the event to fire to consider cancelling the original event + * @param an event that can be fired and is cancellable + * @return true if the event was fired and it caused the original event to be cancelled + */ + public static boolean fireBulkEventToCancel(Cancellable original, T eventToFire) { + Bukkit.getServer().getPluginManager().callEvent(eventToFire); + if (eventToFire.getExplicitResult() == Result.DENY) { + original.setCancelled(true); + return true; + } + + return false; + } + + /** + * Return whether the given damage cause is fire-reltaed. + * + * @param cause the cause + * @return true if fire related + */ + public static boolean isFireCause(DamageCause cause) { + return cause == DamageCause.FIRE || cause == DamageCause.FIRE_TICK; + } + + /** + * Return whether the given cause is an explosion. + * + * @param cause the cause + * @return true if it is an explosion cuase + */ + public static boolean isExplosionCause(DamageCause cause) { + return cause == DamageCause.BLOCK_EXPLOSION || cause == DamageCause.ENTITY_EXPLOSION; + } + + /** + * Restore the statistic associated with the given cause. For example, + * for the {@link DamageCause#DROWNING} cause, the entity would have its + * air level set to its maximum. + * + * @param entity the entity + * @param cause the cuase + */ + public static void restoreStatistic(Entity entity, DamageCause cause) { + if (cause == DamageCause.DROWNING && entity instanceof LivingEntity) { + LivingEntity living = (LivingEntity) entity; + living.setRemainingAir(living.getMaximumAir()); + } + + if (isFireCause(cause)) { + entity.setFireTicks(0); + } + + if (cause == DamageCause.LAVA) { + entity.setFireTicks(0); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/HandlerTracer.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/HandlerTracer.java new file mode 100644 index 000000000..43afd2166 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/HandlerTracer.java @@ -0,0 +1,87 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.google.common.collect.Lists; +import org.bukkit.event.Event; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredListener; + +import javax.annotation.Nullable; +import java.util.List; + +/** + * Traces the owner of a handler. + */ +public class HandlerTracer { + + private final List handlers; + + public HandlerTracer(Event event) { + this.handlers = getHandlers(event); + } + + /** + * Attempt to detect the cause of an event that was fired. + * + * @param elements The stack trace + * @return The plugin, if found + */ + @Nullable + public Plugin detectPlugin(StackTraceElement[] elements) { + for (int i = elements.length - 1; i >= 0; i--) { + StackTraceElement element = elements[i]; + + for (Handler handler : handlers) { + if (element.getClassName().equals(handler.className)) { + return handler.plugin; + } + } + } + + return null; + } + + /** + * Build a cache of listeners registered for an event. + * + * @param event The event + * @return A list of handlers + */ + private static List getHandlers(Event event) { + List handlers = Lists.newArrayList(); + + for (RegisteredListener listener : event.getHandlers().getRegisteredListeners()) { + handlers.add(new Handler(listener.getListener().getClass().getName(), listener.getPlugin())); + } + + return handlers; + } + + private static class Handler { + private final String className; + private final Plugin plugin; + + private Handler(String className, Plugin plugin) { + this.className = className; + this.plugin = plugin; + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/InteropUtils.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/InteropUtils.java new file mode 100644 index 000000000..8bb2ce89e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/InteropUtils.java @@ -0,0 +1,119 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.google.common.base.Function; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.lang.reflect.Method; +import java.util.UUID; + +import javax.annotation.Nullable; + +public final class InteropUtils { + + @Nullable + private static final Class forgeFakePlayerClass; + // This UUID is from Minecraft Forge (see FakePlayerFactory) + private static final UUID forgeFakePlayerUuid = UUID.fromString("41c82c87-7afb-4024-ba57-13d2c99cae77"); + private static final PlayerHandleFunction playerHandleFunction; + + static { + forgeFakePlayerClass = findClass("net.minecraftforge.common.util.FakePlayer"); + + PlayerHandleFunction function; + try { + function = new PlayerHandleFunction(); + } catch (Exception e) { + function = null; + } + playerHandleFunction = function; + } + + @Nullable + private static Class findClass(String name) { + try { + return Class.forName(name); + } catch (Throwable ignored) { + return null; + } + } + + private InteropUtils() { + } + + /** + * Return whether the given player is a fake player. + * + * @param player the player + * @return true if a fake player + */ + public static boolean isFakePlayer(Player player) { + UUID uuid = player.getUniqueId(); + String name = player.getName(); + + if (player.hasMetadata("NPC")) { + return true; + } + + if (uuid.equals(forgeFakePlayerUuid)) { + return true; + } + + if (forgeFakePlayerClass != null && playerHandleFunction != null) { + Object handle = playerHandleFunction.apply(player); + if (handle != null) { + if (forgeFakePlayerClass.isAssignableFrom(handle.getClass())) { + return true; + } + } + } + + if (name.length() >= 3 && name.charAt(0) == '[' && name.charAt(name.length() - 1) == ']') { + return true; + } + + return false; + } + + private static final class PlayerHandleFunction implements Function { + private final Class craftPlayerClass; + private final Method getHandleMethod; + + private PlayerHandleFunction() throws NoSuchMethodException, ClassNotFoundException { + craftPlayerClass = Class.forName(Bukkit.getServer().getClass().getCanonicalName().replaceAll("CraftServer$", "entity.CraftPlayer")); + getHandleMethod = craftPlayerClass.getMethod("getHandle"); + } + + @Nullable + @Override + public Object apply(Object o) { + if (craftPlayerClass.isAssignableFrom(o.getClass())) { + try { + return getHandleMethod.invoke(o); + } catch (Throwable ignored) { + } + } + + return null; + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Materials.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Materials.java new file mode 100644 index 000000000..35b7ce939 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/Materials.java @@ -0,0 +1,1708 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.sk89q.worldguard.protection.flags.Flags; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.entity.EntityType; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Stream; + +/** + * Material utility class. + */ +public final class Materials { + + private static final Logger logger = Logger.getLogger(Materials.class.getSimpleName()); + + private static final int MODIFIED_ON_RIGHT = 1; + private static final int MODIFIED_ON_LEFT = 2; + private static final int MODIFIES_BLOCKS = 4; + + private static final BiMap ENTITY_ITEMS = HashBiMap.create(); + private static final Map MATERIAL_FLAGS = new EnumMap<>(Material.class); + private static final Set DAMAGE_EFFECTS = new HashSet<>(); + + static { + ENTITY_ITEMS.put(EntityType.PAINTING, Material.PAINTING); + ENTITY_ITEMS.put(EntityType.ARROW, Material.ARROW); + ENTITY_ITEMS.put(EntityType.SNOWBALL, Material.SNOWBALL); + ENTITY_ITEMS.put(EntityType.FIREBALL, Material.FIRE_CHARGE); + ENTITY_ITEMS.put(EntityType.ENDER_PEARL, Material.ENDER_PEARL); + ENTITY_ITEMS.put(EntityType.THROWN_EXP_BOTTLE, Material.EXPERIENCE_BOTTLE); + ENTITY_ITEMS.put(EntityType.ITEM_FRAME, Material.ITEM_FRAME); + ENTITY_ITEMS.put(EntityType.GLOW_ITEM_FRAME, Material.GLOW_ITEM_FRAME); + ENTITY_ITEMS.put(EntityType.PRIMED_TNT, Material.TNT); + ENTITY_ITEMS.put(EntityType.FIREWORK, Material.FIREWORK_ROCKET); + ENTITY_ITEMS.put(EntityType.MINECART_COMMAND, Material.COMMAND_BLOCK_MINECART); + ENTITY_ITEMS.put(EntityType.BOAT, Material.OAK_BOAT); + ENTITY_ITEMS.put(EntityType.MINECART, Material.MINECART); + ENTITY_ITEMS.put(EntityType.MINECART_CHEST, Material.CHEST_MINECART); + ENTITY_ITEMS.put(EntityType.MINECART_FURNACE, Material.FURNACE_MINECART); + ENTITY_ITEMS.put(EntityType.MINECART_TNT, Material.TNT_MINECART); + ENTITY_ITEMS.put(EntityType.MINECART_HOPPER, Material.HOPPER_MINECART); + ENTITY_ITEMS.put(EntityType.SPLASH_POTION, Material.POTION); + ENTITY_ITEMS.put(EntityType.EGG, Material.EGG); + ENTITY_ITEMS.put(EntityType.ARMOR_STAND, Material.ARMOR_STAND); + ENTITY_ITEMS.put(EntityType.ENDER_CRYSTAL, Material.END_CRYSTAL); + + MATERIAL_FLAGS.put(Material.AIR, 0); + MATERIAL_FLAGS.put(Material.STONE, 0); + MATERIAL_FLAGS.put(Material.GRASS_BLOCK, 0); + MATERIAL_FLAGS.put(Material.DIRT, 0); + MATERIAL_FLAGS.put(Material.COBBLESTONE, 0); + MATERIAL_FLAGS.put(Material.BEDROCK, 0); + MATERIAL_FLAGS.put(Material.WATER, 0); + MATERIAL_FLAGS.put(Material.LAVA, 0); + MATERIAL_FLAGS.put(Material.SAND, 0); + MATERIAL_FLAGS.put(Material.GRAVEL, 0); + MATERIAL_FLAGS.put(Material.SPONGE, 0); + MATERIAL_FLAGS.put(Material.GLASS, 0); + MATERIAL_FLAGS.put(Material.LAPIS_BLOCK, 0); + MATERIAL_FLAGS.put(Material.DISPENSER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.NOTE_BLOCK, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.POWERED_RAIL, 0); + MATERIAL_FLAGS.put(Material.DETECTOR_RAIL, 0); + MATERIAL_FLAGS.put(Material.STICKY_PISTON, 0); + MATERIAL_FLAGS.put(Material.COBWEB, 0); + MATERIAL_FLAGS.put(Material.GRASS, 0); + MATERIAL_FLAGS.put(Material.DEAD_BUSH, 0); + MATERIAL_FLAGS.put(Material.PISTON, 0); + MATERIAL_FLAGS.put(Material.PISTON_HEAD, 0); + MATERIAL_FLAGS.put(Material.MOVING_PISTON, 0); + MATERIAL_FLAGS.put(Material.SUNFLOWER, 0); + MATERIAL_FLAGS.put(Material.LILAC, 0); + MATERIAL_FLAGS.put(Material.PEONY, 0); + MATERIAL_FLAGS.put(Material.ROSE_BUSH, 0); + MATERIAL_FLAGS.put(Material.BROWN_MUSHROOM, 0); + MATERIAL_FLAGS.put(Material.RED_MUSHROOM, 0); + MATERIAL_FLAGS.put(Material.GOLD_BLOCK, 0); + MATERIAL_FLAGS.put(Material.IRON_BLOCK, 0); + MATERIAL_FLAGS.put(Material.BRICK, 0); + MATERIAL_FLAGS.put(Material.TNT, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BOOKSHELF, 0); + MATERIAL_FLAGS.put(Material.MOSSY_COBBLESTONE, 0); + MATERIAL_FLAGS.put(Material.OBSIDIAN, 0); + MATERIAL_FLAGS.put(Material.TORCH, 0); + MATERIAL_FLAGS.put(Material.FIRE, 0); + MATERIAL_FLAGS.put(Material.SPAWNER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CHEST, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.REDSTONE_WIRE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.DIAMOND_BLOCK, 0); + MATERIAL_FLAGS.put(Material.CRAFTING_TABLE, 0); + MATERIAL_FLAGS.put(Material.WHEAT, 0); + MATERIAL_FLAGS.put(Material.FARMLAND, 0); + MATERIAL_FLAGS.put(Material.FURNACE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.LADDER, 0); + MATERIAL_FLAGS.put(Material.RAIL, 0); + MATERIAL_FLAGS.put(Material.COBBLESTONE_STAIRS, 0); + MATERIAL_FLAGS.put(Material.LEVER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.STONE_PRESSURE_PLATE, 0); + MATERIAL_FLAGS.put(Material.REDSTONE_WALL_TORCH, 0); + MATERIAL_FLAGS.put(Material.REDSTONE_TORCH, 0); + MATERIAL_FLAGS.put(Material.SNOW, 0); + MATERIAL_FLAGS.put(Material.ICE, 0); + MATERIAL_FLAGS.put(Material.SNOW_BLOCK, 0); + MATERIAL_FLAGS.put(Material.CACTUS, 0); + MATERIAL_FLAGS.put(Material.CLAY, 0); + MATERIAL_FLAGS.put(Material.JUKEBOX, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.PUMPKIN, 0); + MATERIAL_FLAGS.put(Material.NETHERRACK, 0); + MATERIAL_FLAGS.put(Material.SOUL_SAND, 0); + MATERIAL_FLAGS.put(Material.GLOWSTONE, 0); + MATERIAL_FLAGS.put(Material.NETHER_PORTAL, 0); + MATERIAL_FLAGS.put(Material.JACK_O_LANTERN, 0); + MATERIAL_FLAGS.put(Material.CAKE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.REPEATER, MODIFIED_ON_RIGHT); +// MATERIAL_FLAGS.put(Material.STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.ACACIA_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BIRCH_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.DARK_OAK_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.JUNGLE_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.OAK_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.SPRUCE_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.INFESTED_STONE, 0); + MATERIAL_FLAGS.put(Material.INFESTED_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.INFESTED_MOSSY_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.INFESTED_CRACKED_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.INFESTED_CHISELED_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.INFESTED_COBBLESTONE, 0); + MATERIAL_FLAGS.put(Material.STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.MOSSY_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CRACKED_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CHISELED_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.BROWN_MUSHROOM_BLOCK, 0); + MATERIAL_FLAGS.put(Material.RED_MUSHROOM_BLOCK, 0); + MATERIAL_FLAGS.put(Material.IRON_BARS, 0); + MATERIAL_FLAGS.put(Material.GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.MELON, 0); + MATERIAL_FLAGS.put(Material.PUMPKIN_STEM, 0); + MATERIAL_FLAGS.put(Material.MELON_STEM, 0); + MATERIAL_FLAGS.put(Material.VINE, 0); + MATERIAL_FLAGS.put(Material.BRICK_STAIRS, 0); + MATERIAL_FLAGS.put(Material.MYCELIUM, 0); + MATERIAL_FLAGS.put(Material.LILY_PAD, 0); + MATERIAL_FLAGS.put(Material.NETHER_BRICK, 0); + MATERIAL_FLAGS.put(Material.NETHER_BRICK_STAIRS, 0); + MATERIAL_FLAGS.put(Material.ENCHANTING_TABLE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BREWING_STAND, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.END_PORTAL, 0); + MATERIAL_FLAGS.put(Material.END_PORTAL_FRAME, 0); + MATERIAL_FLAGS.put(Material.END_STONE, 0); + MATERIAL_FLAGS.put(Material.DRAGON_EGG, MODIFIED_ON_LEFT | MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.REDSTONE_LAMP, 0); + MATERIAL_FLAGS.put(Material.COCOA, 0); + MATERIAL_FLAGS.put(Material.SANDSTONE_STAIRS, 0); + MATERIAL_FLAGS.put(Material.ENDER_CHEST, 0); + MATERIAL_FLAGS.put(Material.TRIPWIRE_HOOK, 0); + MATERIAL_FLAGS.put(Material.TRIPWIRE, 0); + MATERIAL_FLAGS.put(Material.EMERALD_BLOCK, 0); + MATERIAL_FLAGS.put(Material.COMMAND_BLOCK, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BEACON, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.ANVIL, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CHIPPED_ANVIL, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.DAMAGED_ANVIL, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.TRAPPED_CHEST, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.HEAVY_WEIGHTED_PRESSURE_PLATE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_WEIGHTED_PRESSURE_PLATE, 0); + MATERIAL_FLAGS.put(Material.COMPARATOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.DAYLIGHT_DETECTOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.REDSTONE_BLOCK, 0); + MATERIAL_FLAGS.put(Material.NETHER_QUARTZ_ORE, 0); + MATERIAL_FLAGS.put(Material.HOPPER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.QUARTZ_BLOCK, 0); + MATERIAL_FLAGS.put(Material.QUARTZ_STAIRS, 0); + MATERIAL_FLAGS.put(Material.ACTIVATOR_RAIL, 0); + MATERIAL_FLAGS.put(Material.DROPPER, MODIFIED_ON_RIGHT); +// MATERIAL_FLAGS.put(Material.STAINED_CLAY, 0); +// MATERIAL_FLAGS.put(Material.STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.ACACIA_STAIRS, 0); + MATERIAL_FLAGS.put(Material.DARK_OAK_STAIRS, 0); + MATERIAL_FLAGS.put(Material.HAY_BLOCK, 0); +// MATERIAL_FLAGS.put(Material.HARD_CLAY, 0); + MATERIAL_FLAGS.put(Material.COAL_BLOCK, 0); + MATERIAL_FLAGS.put(Material.PACKED_ICE, 0); + MATERIAL_FLAGS.put(Material.TALL_GRASS, 0); + MATERIAL_FLAGS.put(Material.TALL_SEAGRASS, 0); + MATERIAL_FLAGS.put(Material.LARGE_FERN, 0); + + MATERIAL_FLAGS.put(Material.PRISMARINE, 0); + MATERIAL_FLAGS.put(Material.SEA_LANTERN, 0); + MATERIAL_FLAGS.put(Material.SLIME_BLOCK, 0); + MATERIAL_FLAGS.put(Material.IRON_TRAPDOOR, 0); + MATERIAL_FLAGS.put(Material.RED_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.RED_SANDSTONE_STAIRS, 0); + MATERIAL_FLAGS.put(Material.SPRUCE_DOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BIRCH_DOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.JUNGLE_DOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.ACACIA_DOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.DARK_OAK_DOOR, MODIFIED_ON_RIGHT); + + MATERIAL_FLAGS.put(Material.DIRT_PATH, 0); + MATERIAL_FLAGS.put(Material.CHORUS_PLANT, 0); + MATERIAL_FLAGS.put(Material.CHORUS_FLOWER, 0); + MATERIAL_FLAGS.put(Material.BEETROOTS, 0); + MATERIAL_FLAGS.put(Material.END_ROD, 0); + MATERIAL_FLAGS.put(Material.END_STONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.END_GATEWAY, 0); + MATERIAL_FLAGS.put(Material.FROSTED_ICE, 0); + MATERIAL_FLAGS.put(Material.PURPUR_BLOCK, 0); + MATERIAL_FLAGS.put(Material.PURPUR_STAIRS, 0); + MATERIAL_FLAGS.put(Material.PURPUR_PILLAR, 0); + MATERIAL_FLAGS.put(Material.PURPUR_SLAB, 0); + MATERIAL_FLAGS.put(Material.STRUCTURE_BLOCK, MODIFIED_ON_LEFT | MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.REPEATING_COMMAND_BLOCK, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CHAIN_COMMAND_BLOCK , MODIFIED_ON_RIGHT); + + MATERIAL_FLAGS.put(Material.MAGMA_BLOCK, 0); + MATERIAL_FLAGS.put(Material.NETHER_WART_BLOCK, 0); + MATERIAL_FLAGS.put(Material.RED_NETHER_BRICKS, 0); + MATERIAL_FLAGS.put(Material.BONE_BLOCK, 0); + MATERIAL_FLAGS.put(Material.BARRIER, 0); + MATERIAL_FLAGS.put(Material.STRUCTURE_VOID, 0); + // 1.12 + MATERIAL_FLAGS.put(Material.BLACK_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.BLUE_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.BROWN_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.CYAN_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.GRAY_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.GREEN_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.YELLOW_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.LIME_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.ORANGE_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.PINK_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.PURPLE_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.RED_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.WHITE_CONCRETE, 0); + MATERIAL_FLAGS.put(Material.BLACK_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.BLUE_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.BROWN_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.CYAN_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.GRAY_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.GREEN_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.YELLOW_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.LIME_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.ORANGE_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.PINK_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.PURPLE_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.RED_CONCRETE_POWDER, 0); + MATERIAL_FLAGS.put(Material.WHITE_CONCRETE_POWDER, 0); + + MATERIAL_FLAGS.put(Material.WHITE_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.ORANGE_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.YELLOW_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.LIME_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.PINK_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.GRAY_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.CYAN_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.PURPLE_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BLUE_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BROWN_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.GREEN_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.RED_GLAZED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BLACK_GLAZED_TERRACOTTA, 0); + + // 1.13 + MATERIAL_FLAGS.put(Material.ANDESITE, 0); + MATERIAL_FLAGS.put(Material.ATTACHED_MELON_STEM, 0); + MATERIAL_FLAGS.put(Material.ATTACHED_PUMPKIN_STEM, 0); + MATERIAL_FLAGS.put(Material.BLACK_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.BLACK_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.BLACK_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BLUE_ICE, 0); + MATERIAL_FLAGS.put(Material.BLUE_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.BLUE_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.BLUE_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BROWN_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.BROWN_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.BROWN_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.BUBBLE_COLUMN, 0); + MATERIAL_FLAGS.put(Material.CARROTS, 0); + MATERIAL_FLAGS.put(Material.CARVED_PUMPKIN, 0); + MATERIAL_FLAGS.put(Material.CAVE_AIR, 0); + MATERIAL_FLAGS.put(Material.CHISELED_QUARTZ_BLOCK, 0); + MATERIAL_FLAGS.put(Material.CHISELED_RED_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.CHISELED_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.COARSE_DIRT, 0); + MATERIAL_FLAGS.put(Material.CONDUIT, 0); + MATERIAL_FLAGS.put(Material.CUT_RED_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.CUT_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.CYAN_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.CYAN_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.CYAN_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.DARK_PRISMARINE, 0); + MATERIAL_FLAGS.put(Material.DIORITE, 0); + MATERIAL_FLAGS.put(Material.DRIED_KELP_BLOCK, 0); + MATERIAL_FLAGS.put(Material.FERN, 0); + MATERIAL_FLAGS.put(Material.GRANITE, 0); + MATERIAL_FLAGS.put(Material.GRAY_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.GRAY_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.GRAY_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.GREEN_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.GREEN_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.GREEN_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.KELP, 0); + MATERIAL_FLAGS.put(Material.KELP_PLANT, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.LIME_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.LIME_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.LIME_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.MUSHROOM_STEM, 0); + MATERIAL_FLAGS.put(Material.OBSERVER, 0); + MATERIAL_FLAGS.put(Material.ORANGE_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.ORANGE_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.ORANGE_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.PINK_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.PINK_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.PINK_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.PODZOL, 0); + MATERIAL_FLAGS.put(Material.POLISHED_ANDESITE, 0); + MATERIAL_FLAGS.put(Material.POLISHED_DIORITE, 0); + MATERIAL_FLAGS.put(Material.POLISHED_GRANITE, 0); + MATERIAL_FLAGS.put(Material.POTATOES, 0); + MATERIAL_FLAGS.put(Material.PRISMARINE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.PURPLE_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.PURPLE_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.PURPLE_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.QUARTZ_PILLAR, 0); + MATERIAL_FLAGS.put(Material.RED_SAND, 0); + MATERIAL_FLAGS.put(Material.RED_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.RED_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.RED_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.SEAGRASS, 0); + MATERIAL_FLAGS.put(Material.SEA_PICKLE, 0); + MATERIAL_FLAGS.put(Material.SMOOTH_QUARTZ, 0); + MATERIAL_FLAGS.put(Material.SMOOTH_RED_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.SMOOTH_SANDSTONE, 0); + MATERIAL_FLAGS.put(Material.SMOOTH_STONE, 0); + MATERIAL_FLAGS.put(Material.TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.TURTLE_EGG, 0); + MATERIAL_FLAGS.put(Material.VOID_AIR, 0); + MATERIAL_FLAGS.put(Material.WALL_TORCH, 0); + MATERIAL_FLAGS.put(Material.WET_SPONGE, 0); + MATERIAL_FLAGS.put(Material.WHITE_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.WHITE_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.WHITE_TERRACOTTA, 0); + MATERIAL_FLAGS.put(Material.YELLOW_STAINED_GLASS, 0); + MATERIAL_FLAGS.put(Material.YELLOW_STAINED_GLASS_PANE, 0); + MATERIAL_FLAGS.put(Material.YELLOW_TERRACOTTA, 0); + + MATERIAL_FLAGS.put(Material.BAMBOO, 0); + MATERIAL_FLAGS.put(Material.BAMBOO_SAPLING, 0); + MATERIAL_FLAGS.put(Material.BARREL, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BELL, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BLAST_FURNACE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CAMPFIRE, MODIFIED_ON_RIGHT | MODIFIED_ON_LEFT); + MATERIAL_FLAGS.put(Material.CARTOGRAPHY_TABLE, 0); + MATERIAL_FLAGS.put(Material.COMPOSTER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.FLETCHING_TABLE, 0); + MATERIAL_FLAGS.put(Material.GRINDSTONE, 0); + MATERIAL_FLAGS.put(Material.JIGSAW, MODIFIED_ON_RIGHT | MODIFIED_ON_LEFT); + MATERIAL_FLAGS.put(Material.LANTERN, 0); + MATERIAL_FLAGS.put(Material.LECTERN, 0); + MATERIAL_FLAGS.put(Material.LOOM, 0); + MATERIAL_FLAGS.put(Material.SCAFFOLDING, 0); + MATERIAL_FLAGS.put(Material.SMITHING_TABLE, 0); + MATERIAL_FLAGS.put(Material.SMOKER, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.STONECUTTER, 0); + MATERIAL_FLAGS.put(Material.SWEET_BERRY_BUSH, MODIFIED_ON_RIGHT); + + MATERIAL_FLAGS.put(Material.IRON_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.IRON_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.IRON_AXE, 0); + MATERIAL_FLAGS.put(Material.FLINT_AND_STEEL, 0); + MATERIAL_FLAGS.put(Material.APPLE, 0); + MATERIAL_FLAGS.put(Material.BOW, 0); + MATERIAL_FLAGS.put(Material.ARROW, 0); + MATERIAL_FLAGS.put(Material.COAL, 0); + MATERIAL_FLAGS.put(Material.DIAMOND, 0); + MATERIAL_FLAGS.put(Material.IRON_INGOT, 0); + MATERIAL_FLAGS.put(Material.GOLD_INGOT, 0); + MATERIAL_FLAGS.put(Material.IRON_SWORD, 0); + MATERIAL_FLAGS.put(Material.WOODEN_SWORD, 0); + MATERIAL_FLAGS.put(Material.WOODEN_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.WOODEN_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.WOODEN_AXE, 0); + MATERIAL_FLAGS.put(Material.STONE_SWORD, 0); + MATERIAL_FLAGS.put(Material.STONE_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.STONE_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.STONE_AXE, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_SWORD, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_AXE, 0); + MATERIAL_FLAGS.put(Material.STICK, 0); + MATERIAL_FLAGS.put(Material.BOWL, 0); + MATERIAL_FLAGS.put(Material.MUSHROOM_STEW, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_SWORD, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_AXE, 0); + MATERIAL_FLAGS.put(Material.STRING, 0); + MATERIAL_FLAGS.put(Material.FEATHER, 0); + MATERIAL_FLAGS.put(Material.GUNPOWDER, 0); + MATERIAL_FLAGS.put(Material.WOODEN_HOE, 0); + MATERIAL_FLAGS.put(Material.STONE_HOE, 0); + MATERIAL_FLAGS.put(Material.IRON_HOE, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_HOE, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_HOE, 0); + MATERIAL_FLAGS.put(Material.WHEAT_SEEDS, 0); + MATERIAL_FLAGS.put(Material.BREAD, 0); + MATERIAL_FLAGS.put(Material.LEATHER_HELMET, 0); + MATERIAL_FLAGS.put(Material.LEATHER_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.LEATHER_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.LEATHER_BOOTS, 0); + MATERIAL_FLAGS.put(Material.CHAINMAIL_HELMET, 0); + MATERIAL_FLAGS.put(Material.CHAINMAIL_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.CHAINMAIL_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.CHAINMAIL_BOOTS, 0); + MATERIAL_FLAGS.put(Material.IRON_HELMET, 0); + MATERIAL_FLAGS.put(Material.IRON_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.IRON_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.IRON_BOOTS, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_HELMET, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_BOOTS, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_HELMET, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_BOOTS, 0); + MATERIAL_FLAGS.put(Material.FLINT, 0); + MATERIAL_FLAGS.put(Material.PORKCHOP, 0); + MATERIAL_FLAGS.put(Material.COOKED_PORKCHOP, 0); + MATERIAL_FLAGS.put(Material.PAINTING, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_APPLE, 0); + MATERIAL_FLAGS.put(Material.BUCKET, 0); + MATERIAL_FLAGS.put(Material.WATER_BUCKET, 0); + MATERIAL_FLAGS.put(Material.LAVA_BUCKET, 0); + MATERIAL_FLAGS.put(Material.MINECART, 0); + MATERIAL_FLAGS.put(Material.SADDLE, 0); + MATERIAL_FLAGS.put(Material.IRON_DOOR, 0); + MATERIAL_FLAGS.put(Material.REDSTONE, 0); + MATERIAL_FLAGS.put(Material.SNOWBALL, 0); + + MATERIAL_FLAGS.put(Material.LEATHER, 0); + MATERIAL_FLAGS.put(Material.MILK_BUCKET, 0); + MATERIAL_FLAGS.put(Material.BRICKS, 0); + MATERIAL_FLAGS.put(Material.CLAY_BALL, 0); + MATERIAL_FLAGS.put(Material.SUGAR_CANE, 0); + MATERIAL_FLAGS.put(Material.PAPER, 0); + MATERIAL_FLAGS.put(Material.BOOK, 0); + MATERIAL_FLAGS.put(Material.SLIME_BALL, 0); + MATERIAL_FLAGS.put(Material.CHEST_MINECART, 0); + MATERIAL_FLAGS.put(Material.FURNACE_MINECART, 0); + MATERIAL_FLAGS.put(Material.EGG, 0); + MATERIAL_FLAGS.put(Material.COMPASS, 0); + MATERIAL_FLAGS.put(Material.FISHING_ROD, 0); + MATERIAL_FLAGS.put(Material.CLOCK, 0); + MATERIAL_FLAGS.put(Material.GLOWSTONE_DUST, 0); + MATERIAL_FLAGS.put(Material.COD, 0); + MATERIAL_FLAGS.put(Material.COOKED_COD, 0); + MATERIAL_FLAGS.put(Material.INK_SAC, 0); + MATERIAL_FLAGS.put(Material.BLACK_DYE, 0); + MATERIAL_FLAGS.put(Material.BLUE_DYE, 0); + MATERIAL_FLAGS.put(Material.BROWN_DYE, 0); + MATERIAL_FLAGS.put(Material.CYAN_DYE, 0); + MATERIAL_FLAGS.put(Material.GRAY_DYE, 0); + MATERIAL_FLAGS.put(Material.GREEN_DYE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_BLUE_DYE, 0); + MATERIAL_FLAGS.put(Material.LIGHT_GRAY_DYE, 0); + MATERIAL_FLAGS.put(Material.LIME_DYE, 0); + MATERIAL_FLAGS.put(Material.MAGENTA_DYE, 0); + MATERIAL_FLAGS.put(Material.ORANGE_DYE, 0); + MATERIAL_FLAGS.put(Material.PINK_DYE, 0); + MATERIAL_FLAGS.put(Material.PURPLE_DYE, 0); + MATERIAL_FLAGS.put(Material.RED_DYE, 0); + MATERIAL_FLAGS.put(Material.WHITE_DYE, 0); + MATERIAL_FLAGS.put(Material.YELLOW_DYE, 0); + MATERIAL_FLAGS.put(Material.COCOA_BEANS, 0); + MATERIAL_FLAGS.put(Material.BONE_MEAL, MODIFIES_BLOCKS); + MATERIAL_FLAGS.put(Material.BONE, 0); + MATERIAL_FLAGS.put(Material.SUGAR, 0); + MATERIAL_FLAGS.put(Material.COOKIE, 0); + MATERIAL_FLAGS.put(Material.MAP, 0); + MATERIAL_FLAGS.put(Material.SHEARS, 0); + MATERIAL_FLAGS.put(Material.MELON_SLICE, 0); + MATERIAL_FLAGS.put(Material.PUMPKIN_SEEDS, 0); + MATERIAL_FLAGS.put(Material.MELON_SEEDS, 0); + MATERIAL_FLAGS.put(Material.BEEF, 0); + MATERIAL_FLAGS.put(Material.COOKED_BEEF, 0); + MATERIAL_FLAGS.put(Material.CHICKEN, 0); + MATERIAL_FLAGS.put(Material.COOKED_CHICKEN, 0); + MATERIAL_FLAGS.put(Material.ROTTEN_FLESH, 0); + MATERIAL_FLAGS.put(Material.ENDER_PEARL, 0); + MATERIAL_FLAGS.put(Material.BLAZE_ROD, 0); + MATERIAL_FLAGS.put(Material.GHAST_TEAR, 0); + MATERIAL_FLAGS.put(Material.GOLD_NUGGET, 0); + MATERIAL_FLAGS.put(Material.NETHER_WART, 0); + MATERIAL_FLAGS.put(Material.POTION, 0); + MATERIAL_FLAGS.put(Material.GLASS_BOTTLE, 0); + MATERIAL_FLAGS.put(Material.SPIDER_EYE, 0); + MATERIAL_FLAGS.put(Material.FERMENTED_SPIDER_EYE, 0); + MATERIAL_FLAGS.put(Material.BLAZE_POWDER, 0); + MATERIAL_FLAGS.put(Material.MAGMA_CREAM, 0); + MATERIAL_FLAGS.put(Material.ENDER_EYE, 0); + MATERIAL_FLAGS.put(Material.GLISTERING_MELON_SLICE, 0); + MATERIAL_FLAGS.put(Material.EXPERIENCE_BOTTLE, 0); + MATERIAL_FLAGS.put(Material.FIRE_CHARGE, 0); + MATERIAL_FLAGS.put(Material.WRITABLE_BOOK, 0); + MATERIAL_FLAGS.put(Material.WRITTEN_BOOK, 0); + MATERIAL_FLAGS.put(Material.EMERALD, 0); + MATERIAL_FLAGS.put(Material.ITEM_FRAME, 0); + MATERIAL_FLAGS.put(Material.CARROT, 0); + MATERIAL_FLAGS.put(Material.POTATO, 0); + MATERIAL_FLAGS.put(Material.BAKED_POTATO, 0); + MATERIAL_FLAGS.put(Material.POISONOUS_POTATO, 0); + MATERIAL_FLAGS.put(Material.FILLED_MAP, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_CARROT, 0); + MATERIAL_FLAGS.put(Material.CREEPER_HEAD, 0); + MATERIAL_FLAGS.put(Material.CREEPER_WALL_HEAD, 0); + MATERIAL_FLAGS.put(Material.DRAGON_HEAD, 0); + MATERIAL_FLAGS.put(Material.DRAGON_WALL_HEAD, 0); + MATERIAL_FLAGS.put(Material.PLAYER_HEAD, 0); + MATERIAL_FLAGS.put(Material.PLAYER_WALL_HEAD, 0); + MATERIAL_FLAGS.put(Material.ZOMBIE_HEAD, 0); + MATERIAL_FLAGS.put(Material.ZOMBIE_WALL_HEAD, 0); + MATERIAL_FLAGS.put(Material.SKELETON_SKULL, 0); + MATERIAL_FLAGS.put(Material.SKELETON_WALL_SKULL, 0); + MATERIAL_FLAGS.put(Material.WITHER_SKELETON_SKULL, 0); + MATERIAL_FLAGS.put(Material.WITHER_SKELETON_WALL_SKULL, 0); + MATERIAL_FLAGS.put(Material.CARROT_ON_A_STICK, 0); + MATERIAL_FLAGS.put(Material.NETHER_STAR, 0); + MATERIAL_FLAGS.put(Material.PUMPKIN_PIE, 0); + MATERIAL_FLAGS.put(Material.FIREWORK_ROCKET, 0); + MATERIAL_FLAGS.put(Material.FIREWORK_STAR, 0); + MATERIAL_FLAGS.put(Material.ENCHANTED_BOOK, 0); + MATERIAL_FLAGS.put(Material.NETHER_BRICKS, 0); + MATERIAL_FLAGS.put(Material.QUARTZ, 0); + MATERIAL_FLAGS.put(Material.TNT_MINECART, 0); + MATERIAL_FLAGS.put(Material.HOPPER_MINECART, 0); + MATERIAL_FLAGS.put(Material.LEAD, 0); + MATERIAL_FLAGS.put(Material.NAME_TAG, 0); + MATERIAL_FLAGS.put(Material.COMMAND_BLOCK_MINECART, 0); + + MATERIAL_FLAGS.put(Material.PRISMARINE_SHARD, 0); + MATERIAL_FLAGS.put(Material.PRISMARINE_CRYSTALS, 0); + MATERIAL_FLAGS.put(Material.RABBIT, 0); + MATERIAL_FLAGS.put(Material.COOKED_RABBIT, 0); + MATERIAL_FLAGS.put(Material.RABBIT_STEW, 0); + MATERIAL_FLAGS.put(Material.RABBIT_FOOT, 0); + MATERIAL_FLAGS.put(Material.RABBIT_HIDE, 0); + MATERIAL_FLAGS.put(Material.ARMOR_STAND, 0); + MATERIAL_FLAGS.put(Material.LEATHER_HORSE_ARMOR, 0); + MATERIAL_FLAGS.put(Material.IRON_HORSE_ARMOR, 0); + MATERIAL_FLAGS.put(Material.GOLDEN_HORSE_ARMOR, 0); + MATERIAL_FLAGS.put(Material.DIAMOND_HORSE_ARMOR, 0); + MATERIAL_FLAGS.put(Material.MUTTON, 0); + MATERIAL_FLAGS.put(Material.COOKED_MUTTON, 0); + + MATERIAL_FLAGS.put(Material.BEETROOT, 0); + MATERIAL_FLAGS.put(Material.BEETROOT_SOUP, 0); + MATERIAL_FLAGS.put(Material.BEETROOT_SEEDS, 0); + MATERIAL_FLAGS.put(Material.CHORUS_FRUIT, 0); + MATERIAL_FLAGS.put(Material.POPPED_CHORUS_FRUIT, 0); + MATERIAL_FLAGS.put(Material.SHIELD, 0); + MATERIAL_FLAGS.put(Material.SPECTRAL_ARROW, 0); + MATERIAL_FLAGS.put(Material.TIPPED_ARROW, 0); + MATERIAL_FLAGS.put(Material.DRAGON_BREATH, 0); + MATERIAL_FLAGS.put(Material.LINGERING_POTION, 0); + MATERIAL_FLAGS.put(Material.ELYTRA, 0); + MATERIAL_FLAGS.put(Material.END_CRYSTAL, 0); + + MATERIAL_FLAGS.put(Material.TOTEM_OF_UNDYING, 0); + MATERIAL_FLAGS.put(Material.SHULKER_SHELL, 0); + MATERIAL_FLAGS.put(Material.KNOWLEDGE_BOOK, 0); + + MATERIAL_FLAGS.put(Material.CHARCOAL, 0); + MATERIAL_FLAGS.put(Material.COD_BUCKET, 0); + MATERIAL_FLAGS.put(Material.COOKED_SALMON, 0); + MATERIAL_FLAGS.put(Material.DEBUG_STICK, 0); + MATERIAL_FLAGS.put(Material.DRIED_KELP, 0); + MATERIAL_FLAGS.put(Material.ENCHANTED_GOLDEN_APPLE, 0); + MATERIAL_FLAGS.put(Material.HEART_OF_THE_SEA, 0); + MATERIAL_FLAGS.put(Material.IRON_NUGGET, 0); + MATERIAL_FLAGS.put(Material.LAPIS_LAZULI, 0); + MATERIAL_FLAGS.put(Material.NAUTILUS_SHELL, 0); + MATERIAL_FLAGS.put(Material.PHANTOM_MEMBRANE, 0); + MATERIAL_FLAGS.put(Material.PUFFERFISH, 0); + MATERIAL_FLAGS.put(Material.PUFFERFISH_BUCKET, 0); + MATERIAL_FLAGS.put(Material.SALMON, 0); + MATERIAL_FLAGS.put(Material.SALMON_BUCKET, 0); + MATERIAL_FLAGS.put(Material.SCUTE, 0); + MATERIAL_FLAGS.put(Material.SPLASH_POTION, 0); + MATERIAL_FLAGS.put(Material.TURTLE_HELMET, 0); + MATERIAL_FLAGS.put(Material.TRIDENT, 0); + MATERIAL_FLAGS.put(Material.TROPICAL_FISH, 0); + MATERIAL_FLAGS.put(Material.TROPICAL_FISH_BUCKET, 0); + + MATERIAL_FLAGS.put(Material.CREEPER_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.FLOWER_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.GLOBE_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.MOJANG_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.SKULL_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.CROSSBOW, 0); + MATERIAL_FLAGS.put(Material.SUSPICIOUS_STEW, 0); + MATERIAL_FLAGS.put(Material.SWEET_BERRIES, 0); + + // 1.15 + MATERIAL_FLAGS.put(Material.BEEHIVE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.BEE_NEST, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.HONEY_BLOCK, 0); + MATERIAL_FLAGS.put(Material.HONEYCOMB_BLOCK, 0); + MATERIAL_FLAGS.put(Material.HONEY_BOTTLE, 0); + MATERIAL_FLAGS.put(Material.HONEYCOMB, 0); + + // 1.16 + MATERIAL_FLAGS.put(Material.ANCIENT_DEBRIS, 0); + MATERIAL_FLAGS.put(Material.BASALT, 0); + MATERIAL_FLAGS.put(Material.BLACKSTONE, 0); + MATERIAL_FLAGS.put(Material.CHAIN, 0); + MATERIAL_FLAGS.put(Material.CHISELED_NETHER_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CHISELED_POLISHED_BLACKSTONE, 0); + MATERIAL_FLAGS.put(Material.CRACKED_NETHER_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CRACKED_POLISHED_BLACKSTONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CRIMSON_FUNGUS, 0); + MATERIAL_FLAGS.put(Material.CRIMSON_NYLIUM, 0); + MATERIAL_FLAGS.put(Material.CRIMSON_ROOTS, 0); + MATERIAL_FLAGS.put(Material.CRIMSON_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CRYING_OBSIDIAN, 0); + MATERIAL_FLAGS.put(Material.GILDED_BLACKSTONE, 0); + MATERIAL_FLAGS.put(Material.LODESTONE, 0); + + MATERIAL_FLAGS.put(Material.NETHERITE_AXE, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_BLOCK, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_BOOTS, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_CHESTPLATE, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_HELMET, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_HOE, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_INGOT, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_LEGGINGS, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_PICKAXE, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_SCRAP, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_SHOVEL, 0); + MATERIAL_FLAGS.put(Material.NETHERITE_SWORD, 0); + + MATERIAL_FLAGS.put(Material.NETHER_SPROUTS, 0); + MATERIAL_FLAGS.put(Material.PIGLIN_BANNER_PATTERN, 0); + MATERIAL_FLAGS.put(Material.POLISHED_BASALT, 0); + MATERIAL_FLAGS.put(Material.POLISHED_BLACKSTONE, 0); + MATERIAL_FLAGS.put(Material.POLISHED_BLACKSTONE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.POLISHED_BLACKSTONE_PRESSURE_PLATE, 0); + MATERIAL_FLAGS.put(Material.QUARTZ_BRICKS, 0); + MATERIAL_FLAGS.put(Material.RESPAWN_ANCHOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.SHROOMLIGHT, 0); + MATERIAL_FLAGS.put(Material.SOUL_CAMPFIRE, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.SOUL_FIRE, 0); + MATERIAL_FLAGS.put(Material.SOUL_LANTERN, 0); + MATERIAL_FLAGS.put(Material.SOUL_SOIL, 0); + MATERIAL_FLAGS.put(Material.SOUL_TORCH, 0); + MATERIAL_FLAGS.put(Material.SOUL_WALL_TORCH, 0); + MATERIAL_FLAGS.put(Material.TARGET, 0); + MATERIAL_FLAGS.put(Material.TWISTING_VINES, 0); + MATERIAL_FLAGS.put(Material.TWISTING_VINES_PLANT, 0); + + MATERIAL_FLAGS.put(Material.WARPED_FUNGUS, 0); + MATERIAL_FLAGS.put(Material.WARPED_FUNGUS_ON_A_STICK, 0); + MATERIAL_FLAGS.put(Material.WARPED_NYLIUM, 0); + MATERIAL_FLAGS.put(Material.WARPED_ROOTS, 0); + MATERIAL_FLAGS.put(Material.WARPED_TRAPDOOR, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.WARPED_WART_BLOCK, 0); + MATERIAL_FLAGS.put(Material.WEEPING_VINES, 0); + MATERIAL_FLAGS.put(Material.WEEPING_VINES_PLANT, 0); + + // 1.17 + MATERIAL_FLAGS.put(Material.DEEPSLATE, 0); + MATERIAL_FLAGS.put(Material.COBBLED_DEEPSLATE, 0); + MATERIAL_FLAGS.put(Material.POLISHED_DEEPSLATE, 0); + MATERIAL_FLAGS.put(Material.CALCITE, 0); + MATERIAL_FLAGS.put(Material.TUFF, 0); + MATERIAL_FLAGS.put(Material.DRIPSTONE_BLOCK, 0); + MATERIAL_FLAGS.put(Material.ROOTED_DIRT, 0); + + MATERIAL_FLAGS.put(Material.RAW_IRON_BLOCK, 0); + MATERIAL_FLAGS.put(Material.RAW_COPPER_BLOCK, 0); + MATERIAL_FLAGS.put(Material.RAW_GOLD_BLOCK, 0); + MATERIAL_FLAGS.put(Material.AMETHYST_BLOCK, 0); + MATERIAL_FLAGS.put(Material.BUDDING_AMETHYST, 0); + + MATERIAL_FLAGS.put(Material.EXPOSED_COPPER, 0); + MATERIAL_FLAGS.put(Material.WEATHERED_COPPER, 0); + MATERIAL_FLAGS.put(Material.OXIDIZED_COPPER, 0); + MATERIAL_FLAGS.put(Material.CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.EXPOSED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.WEATHERED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.OXIDIZED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_COPPER_BLOCK, 0); + MATERIAL_FLAGS.put(Material.WAXED_EXPOSED_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_WEATHERED_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_OXIDIZED_COPPER, 0); + MATERIAL_FLAGS.put(Material.AZALEA, 0); + MATERIAL_FLAGS.put(Material.FLOWERING_AZALEA, 0); + + MATERIAL_FLAGS.put(Material.COPPER_BLOCK, 0); + MATERIAL_FLAGS.put(Material.WAXED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_EXPOSED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_WEATHERED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.WAXED_OXIDIZED_CUT_COPPER, 0); + MATERIAL_FLAGS.put(Material.TINTED_GLASS, 0); + MATERIAL_FLAGS.put(Material.SPORE_BLOSSOM, 0); + MATERIAL_FLAGS.put(Material.MOSS_CARPET, 0); + MATERIAL_FLAGS.put(Material.BIG_DRIPLEAF, 0); + MATERIAL_FLAGS.put(Material.BIG_DRIPLEAF_STEM, 0); + MATERIAL_FLAGS.put(Material.SMALL_DRIPLEAF, 0); + MATERIAL_FLAGS.put(Material.SMOOTH_BASALT, 0); + MATERIAL_FLAGS.put(Material.INFESTED_DEEPSLATE, 0); + MATERIAL_FLAGS.put(Material.DEEPSLATE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.CRACKED_DEEPSLATE_BRICKS, 0); + MATERIAL_FLAGS.put(Material.DEEPSLATE_TILES, 0); + MATERIAL_FLAGS.put(Material.CRACKED_DEEPSLATE_TILES, 0); + MATERIAL_FLAGS.put(Material.CHISELED_DEEPSLATE, 0); + MATERIAL_FLAGS.put(Material.GLOW_LICHEN, 0); + MATERIAL_FLAGS.put(Material.LIGHT, 0); + MATERIAL_FLAGS.put(Material.LIGHTNING_ROD, 0); + MATERIAL_FLAGS.put(Material.SCULK_SENSOR, 0); + MATERIAL_FLAGS.put(Material.AMETHYST_SHARD, 0); + MATERIAL_FLAGS.put(Material.RAW_IRON, 0); + MATERIAL_FLAGS.put(Material.RAW_COPPER, 0); + MATERIAL_FLAGS.put(Material.COPPER_INGOT, 0); + MATERIAL_FLAGS.put(Material.RAW_GOLD, 0); + MATERIAL_FLAGS.put(Material.POWDER_SNOW_BUCKET, 0); + MATERIAL_FLAGS.put(Material.AXOLOTL_BUCKET, 0); + MATERIAL_FLAGS.put(Material.BUNDLE, 0); + MATERIAL_FLAGS.put(Material.SPYGLASS, 0); + MATERIAL_FLAGS.put(Material.GLOW_INK_SAC, 0); + MATERIAL_FLAGS.put(Material.GLOW_ITEM_FRAME, 0); + MATERIAL_FLAGS.put(Material.GLOW_BERRIES, 0); + + MATERIAL_FLAGS.put(Material.SMALL_AMETHYST_BUD, 0); + MATERIAL_FLAGS.put(Material.MEDIUM_AMETHYST_BUD, 0); + MATERIAL_FLAGS.put(Material.LARGE_AMETHYST_BUD, 0); + MATERIAL_FLAGS.put(Material.AMETHYST_CLUSTER, 0); + MATERIAL_FLAGS.put(Material.POWDER_SNOW, 0); + + MATERIAL_FLAGS.put(Material.CAVE_VINES, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.CAVE_VINES_PLANT, MODIFIED_ON_RIGHT); + MATERIAL_FLAGS.put(Material.MOSS_BLOCK, 0); + MATERIAL_FLAGS.put(Material.HANGING_ROOTS, 0); + MATERIAL_FLAGS.put(Material.POINTED_DRIPSTONE, 0); + + // Generated via tag + for (Material woodenDoor : Tag.WOODEN_DOORS.getValues()) { + MATERIAL_FLAGS.put(woodenDoor, MODIFIED_ON_RIGHT); + } + for (Material woodenTrapdoor : Tag.WOODEN_TRAPDOORS.getValues()) { + MATERIAL_FLAGS.put(woodenTrapdoor, MODIFIED_ON_RIGHT); + } + for (Material shulkerBox : Tag.SHULKER_BOXES.getValues()) { + MATERIAL_FLAGS.put(shulkerBox, MODIFIED_ON_RIGHT); + } + for (Material boat : Tag.ITEMS_BOATS.getValues()) { + MATERIAL_FLAGS.put(boat, 0); + } + for (Material banner : Tag.BANNERS.getValues()) { + MATERIAL_FLAGS.put(banner, 0); + } + for (Material slab : Tag.SLABS.getValues()) { + MATERIAL_FLAGS.put(slab, 0); + } + for (Material plank : Tag.PLANKS.getValues()) { + MATERIAL_FLAGS.put(plank, 0); + } + for (Material carpet : Tag.CARPETS.getValues()) { + MATERIAL_FLAGS.put(carpet, 0); + } + for (Material sapling : Tag.SAPLINGS.getValues()) { + MATERIAL_FLAGS.put(sapling, 0); + } + for (Material log : Tag.LOGS.getValues()) { + MATERIAL_FLAGS.put(log, 0); + } + for (Material leaves : Tag.LEAVES.getValues()) { + MATERIAL_FLAGS.put(leaves, 0); + } + for (Material stair : Tag.STAIRS.getValues()) { + MATERIAL_FLAGS.put(stair, 0); + } + for (Material wool : Tag.WOOL.getValues()) { + MATERIAL_FLAGS.put(wool, 0); + } + for (Material plate : Tag.WOODEN_PRESSURE_PLATES.getValues()) { + MATERIAL_FLAGS.put(plate, 0); + } + for (Material button : Tag.BUTTONS.getValues()) { + MATERIAL_FLAGS.put(button, MODIFIED_ON_RIGHT); + } + for (Material pot : Tag.FLOWER_POTS.getValues()) { + MATERIAL_FLAGS.put(pot, MODIFIED_ON_RIGHT); + } + for (Material wall : Tag.WALLS.getValues()) { + MATERIAL_FLAGS.put(wall, 0); + } + for (Material sign : Tag.SIGNS.getValues()) { + MATERIAL_FLAGS.put(sign, 0); + } + for (Material flower : Tag.SMALL_FLOWERS.getValues()) { + MATERIAL_FLAGS.put(flower, 0); + } + for (Material bed : Tag.BEDS.getValues()) { + MATERIAL_FLAGS.put(bed, MODIFIED_ON_RIGHT); + } + for (Material musicDisc : Tag.ITEMS_MUSIC_DISCS.getValues()) { + MATERIAL_FLAGS.put(musicDisc, 0); + } + for (Material bannerPat : Tag.ITEMS_BANNERS.getValues()) { + MATERIAL_FLAGS.put(bannerPat, 0); + } + for (Material fenceGate : Tag.FENCE_GATES.getValues()) { + MATERIAL_FLAGS.put(fenceGate, MODIFIED_ON_RIGHT); + } + for (Material fence : Tag.FENCES.getValues()) { + MATERIAL_FLAGS.put(fence, 0); + } for (Material coalOre : Tag.COAL_ORES.getValues()) { + MATERIAL_FLAGS.put(coalOre, 0); + } + for (Material ironOre : Tag.IRON_ORES.getValues()) { + MATERIAL_FLAGS.put(ironOre, 0); + } + for (Material goldOre : Tag.GOLD_ORES.getValues()) { + MATERIAL_FLAGS.put(goldOre, 0); + } + for (Material diamondOre : Tag.DIAMOND_ORES.getValues()) { + MATERIAL_FLAGS.put(diamondOre, 0); + } + for (Material redstoneOre : Tag.REDSTONE_ORES.getValues()) { + MATERIAL_FLAGS.put(redstoneOre, 0); + } + for (Material copperOre : Tag.COPPER_ORES.getValues()) { + MATERIAL_FLAGS.put(copperOre, 0); + } + for (Material emeraldOre : Tag.EMERALD_ORES.getValues()) { + MATERIAL_FLAGS.put(emeraldOre, 0); + } + for (Material lapisOre : Tag.LAPIS_ORES.getValues()) { + MATERIAL_FLAGS.put(lapisOre, 0); + } + for (Material candle : Tag.CANDLES.getValues()) { + MATERIAL_FLAGS.put(candle, MODIFIED_ON_RIGHT); + } + for (Material candleCakes : Tag.CANDLE_CAKES.getValues()) { + MATERIAL_FLAGS.put(candleCakes, MODIFIED_ON_RIGHT); + } + for (Material cauldron : Tag.CAULDRONS.getValues()) { + MATERIAL_FLAGS.put(cauldron, MODIFIED_ON_RIGHT); + } + + Stream.concat(Stream.concat( + Tag.CORAL_BLOCKS.getValues().stream(), + Tag.CORALS.getValues().stream()), + Tag.WALL_CORALS.getValues().stream()).forEach(m -> { + MATERIAL_FLAGS.put(m, 0); + Material dead = Material.getMaterial("DEAD_" + m.name()); + if (dead != null) { + MATERIAL_FLAGS.put(dead, 0); + } + }); + + // Check for missing items/blocks + for (Material material : Material.values()) { + //noinspection deprecation + if (material.isLegacy()) continue; + // Add spawn eggs + if (isSpawnEgg(material)) { + MATERIAL_FLAGS.put(material, 0); + } + if (!MATERIAL_FLAGS.containsKey(material)) { + logger.fine("Missing material definition for " + (material.isBlock() ? "block " : "item ") + material.name()); + } + } + +// DAMAGE_EFFECTS.add(PotionEffectType.SPEED); + DAMAGE_EFFECTS.add(PotionEffectType.SLOW); +// DAMAGE_EFFECTS.add(PotionEffectType.FAST_DIGGING); + DAMAGE_EFFECTS.add(PotionEffectType.SLOW_DIGGING); +// DAMAGE_EFFECTS.add(PotionEffectType.INCREASE_DAMAGE); +// DAMAGE_EFFECTS.add(PotionEffectType.HEAL); + DAMAGE_EFFECTS.add(PotionEffectType.HARM); +// DAMAGE_EFFECTS.add(PotionEffectType.JUMP); + DAMAGE_EFFECTS.add(PotionEffectType.CONFUSION); +// DAMAGE_EFFECTS.add(PotionEffectType.REGENERATION); +// DAMAGE_EFFECTS.add(PotionEffectType.DAMAGE_RESISTANCE); +// DAMAGE_EFFECTS.add(PotionEffectType.FIRE_RESISTANCE); +// DAMAGE_EFFECTS.add(PotionEffectType.WATER_BREATHING); +// DAMAGE_EFFECTS.add(PotionEffectType.INVISIBILITY); + DAMAGE_EFFECTS.add(PotionEffectType.BLINDNESS); +// DAMAGE_EFFECTS.add(PotionEffectType.NIGHT_VISION); + DAMAGE_EFFECTS.add(PotionEffectType.HUNGER); + DAMAGE_EFFECTS.add(PotionEffectType.WEAKNESS); + DAMAGE_EFFECTS.add(PotionEffectType.POISON); + DAMAGE_EFFECTS.add(PotionEffectType.WITHER); +// DAMAGE_EFFECTS.add(PotionEffectType.HEALTH_BOOST); +// DAMAGE_EFFECTS.add(PotionEffectType.ABSORPTION); +// DAMAGE_EFFECTS.add(PotionEffectType.SATURATION); + DAMAGE_EFFECTS.add(PotionEffectType.GLOWING); + DAMAGE_EFFECTS.add(PotionEffectType.LEVITATION); +// DAMAGE_EFFECTS.add(PotionEffectType.LUCK); + DAMAGE_EFFECTS.add(PotionEffectType.UNLUCK); +// DAMAGE_EFFECTS.add(PotionEffectType.SLOW_FALLING); +// DAMAGE_EFFECTS.add(PotionEffectType.CONDUIT_POWER); +// DAMAGE_EFFECTS.add(PotionEffectType.DOLPHINS_GRACE); + DAMAGE_EFFECTS.add(PotionEffectType.BAD_OMEN); +// DAMAGE_EFFECTS.add(PotionEffectType.HERO_OF_THE_VILLAGE); + } + + private Materials() { + } + + /** + * Get the related material for an entity type. + * + * @param type the entity type + * @return the related material or {@code null} if one is not known or exists + */ + @Nullable + public static Material getRelatedMaterial(EntityType type) { + return ENTITY_ITEMS.get(type); + } + + /** + * Get the related entity type for a material. + * + * @param material the material + * @return the related entity type or {@code null} if one is not known or exists + */ + @Nullable + public static EntityType getRelatedEntity(Material material) { + return ENTITY_ITEMS.inverse().get(material); + } + + /** + * Get the material of the block placed by the given bucket, defaulting + * to water if the bucket type is not known. + * + *

If a non-bucket material is given, it will be assumed to be + * an unknown bucket type. If the given bucket doesn't have a block form + * (it can't be placed), then water will be returned (i.e. for milk). + * Be aware that either the stationary or non-stationary material may be + * returned.

+ * + * @param type the bucket material + * @return the block material + */ + public static Material getBucketBlockMaterial(Material type) { + switch (type) { + case LAVA_BUCKET: + return Material.LAVA; + case WATER_BUCKET: + default: + return Material.WATER; + } + } + + /** + * Test whether the given material is a mushroom. + * + * @param material the material + * @return true if a mushroom block + */ + public static boolean isMushroom(Material material) { + return material == Material.RED_MUSHROOM || material == Material.BROWN_MUSHROOM; + } + + /** + * Test whether the given material is a leaf block. + * + * @param material the material + * @return true if a leaf block + */ + public static boolean isLeaf(Material material) { + return Tag.LEAVES.isTagged(material); + } + + /** + * Test whether the given material is a liquid block. + * + * @param material the material + * @return true if a liquid block + */ + public static boolean isLiquid(Material material) { + return isWater(material) || isLava(material); + } + + /** + * Test whether the given material is water. + * + * @param material the material + * @return true if a water block + */ + public static boolean isWater(Material material) { + return material == Material.WATER || material == Material.BUBBLE_COLUMN + || material == Material.KELP_PLANT || material == Material.SEAGRASS || material == Material.TALL_SEAGRASS; + } + + /** + * Test whether the given material is lava. + * + * @param material the material + * @return true if a lava block + */ + public static boolean isLava(Material material) { + return material == Material.LAVA; + } + + /** + * Test whether the given material is a portal material. + * + * @param material the material + * @return true if a portal block + */ + public static boolean isPortal(Material material) { + return material == Material.NETHER_PORTAL || material == Material.END_PORTAL; + } + + /** + * Test whether the given material is a rail block. + * + * @param material the material + * @return true if a rail block + */ + public static boolean isRailBlock(Material material) { + return Tag.RAILS.isTagged(material); + } + + /** + * Test whether the given material is a piston block, not including + * the "technical blocks" such as the piston extension block. + * + * @param material the material + * @return true if a piston block + */ + public static boolean isPistonBlock(Material material) { + return material == Material.PISTON + || material == Material.STICKY_PISTON + || material == Material.MOVING_PISTON; + } + + /** + * Test whether the given material is a Minecart. + * + * @param material the material + * @return true if a Minecart item + */ + public static boolean isMinecart(Material material) { + return material == Material.MINECART + || material == Material.COMMAND_BLOCK_MINECART + || material == Material.TNT_MINECART + || material == Material.HOPPER_MINECART + || material == Material.FURNACE_MINECART + || material == Material.CHEST_MINECART; + } + + /** + * Test whether the given material is a Boat. + * + * @param material the material + * @return true if a Boat item + */ + public static boolean isBoat(Material material) { + return Tag.ITEMS_BOATS.isTagged(material); + } + + /** + * Test whether the given material is a Shulker Box. + * + * @param material the material + * @return true if a Shulker Box block + */ + public static boolean isShulkerBox(Material material) { + return Tag.SHULKER_BOXES.isTagged(material); + } + + /** + * Test whether the given material is an inventory block. + * + * @param material the material + * @return true if an inventory block + */ + public static boolean isInventoryBlock(Material material) { + return material == Material.CHEST + || material == Material.JUKEBOX + || material == Material.DISPENSER + || material == Material.FURNACE + || material == Material.BREWING_STAND + || material == Material.TRAPPED_CHEST + || material == Material.HOPPER + || material == Material.DROPPER + || material == Material.BARREL + || material == Material.BLAST_FURNACE + || material == Material.SMOKER + || Tag.SHULKER_BOXES.isTagged(material); + } + + public static boolean isSpawnEgg(Material material) { + switch (material) { + case AXOLOTL_SPAWN_EGG: + case BAT_SPAWN_EGG: + case BEE_SPAWN_EGG: + case BLAZE_SPAWN_EGG: + case CAT_SPAWN_EGG: + case CAVE_SPIDER_SPAWN_EGG: + case CHICKEN_SPAWN_EGG: + case COD_SPAWN_EGG: + case COW_SPAWN_EGG: + case CREEPER_SPAWN_EGG: + case DOLPHIN_SPAWN_EGG: + case DONKEY_SPAWN_EGG: + case DROWNED_SPAWN_EGG: + case ELDER_GUARDIAN_SPAWN_EGG: + case ENDERMAN_SPAWN_EGG: + case ENDERMITE_SPAWN_EGG: + case EVOKER_SPAWN_EGG: + case FOX_SPAWN_EGG: + case GHAST_SPAWN_EGG: + case GLOW_SQUID_SPAWN_EGG: + case GOAT_SPAWN_EGG: + case GUARDIAN_SPAWN_EGG: + case HOGLIN_SPAWN_EGG: + case HORSE_SPAWN_EGG: + case HUSK_SPAWN_EGG: + case LLAMA_SPAWN_EGG: + case MAGMA_CUBE_SPAWN_EGG: + case MOOSHROOM_SPAWN_EGG: + case MULE_SPAWN_EGG: + case OCELOT_SPAWN_EGG: + case PANDA_SPAWN_EGG: + case PARROT_SPAWN_EGG: + case PHANTOM_SPAWN_EGG: + case PIGLIN_BRUTE_SPAWN_EGG: + case PIGLIN_SPAWN_EGG: + case PIG_SPAWN_EGG: + case PILLAGER_SPAWN_EGG: + case POLAR_BEAR_SPAWN_EGG: + case PUFFERFISH_SPAWN_EGG: + case RABBIT_SPAWN_EGG: + case RAVAGER_SPAWN_EGG: + case SALMON_SPAWN_EGG: + case SHEEP_SPAWN_EGG: + case SHULKER_SPAWN_EGG: + case SILVERFISH_SPAWN_EGG: + case SKELETON_HORSE_SPAWN_EGG: + case SKELETON_SPAWN_EGG: + case SLIME_SPAWN_EGG: + case SPIDER_SPAWN_EGG: + case SQUID_SPAWN_EGG: + case STRAY_SPAWN_EGG: + case STRIDER_SPAWN_EGG: + case TRADER_LLAMA_SPAWN_EGG: + case TROPICAL_FISH_SPAWN_EGG: + case TURTLE_SPAWN_EGG: + case VEX_SPAWN_EGG: + case VILLAGER_SPAWN_EGG: + case VINDICATOR_SPAWN_EGG: + case WANDERING_TRADER_SPAWN_EGG: + case WITCH_SPAWN_EGG: + case WITHER_SKELETON_SPAWN_EGG: + case WOLF_SPAWN_EGG: + case ZOGLIN_SPAWN_EGG: + case ZOMBIE_HORSE_SPAWN_EGG: + case ZOMBIFIED_PIGLIN_SPAWN_EGG: + case ZOMBIE_SPAWN_EGG: + case ZOMBIE_VILLAGER_SPAWN_EGG: + return true; + default: + return false; + } + } + + public static EntityType getEntitySpawnEgg(Material material) { + switch (material) { + case AXOLOTL_SPAWN_EGG: + return EntityType.AXOLOTL; + case SPIDER_SPAWN_EGG: + return EntityType.SPIDER; + case BAT_SPAWN_EGG: + return EntityType.BAT; + case BEE_SPAWN_EGG: + return EntityType.BEE; + case BLAZE_SPAWN_EGG: + return EntityType.BLAZE; + case CAT_SPAWN_EGG: + return EntityType.CAT; + case CAVE_SPIDER_SPAWN_EGG: + return EntityType.CAVE_SPIDER; + case CHICKEN_SPAWN_EGG: + return EntityType.CHICKEN; + case COD_SPAWN_EGG: + return EntityType.COD; + case COW_SPAWN_EGG: + return EntityType.COW; + case CREEPER_SPAWN_EGG: + return EntityType.CREEPER; + case DOLPHIN_SPAWN_EGG: + return EntityType.DOLPHIN; + case DONKEY_SPAWN_EGG: + return EntityType.DONKEY; + case DROWNED_SPAWN_EGG: + return EntityType.DROWNED; + case ELDER_GUARDIAN_SPAWN_EGG: + return EntityType.ELDER_GUARDIAN; + case ENDERMAN_SPAWN_EGG: + return EntityType.ENDERMAN; + case ENDERMITE_SPAWN_EGG: + return EntityType.ENDERMITE; + case EVOKER_SPAWN_EGG: + return EntityType.EVOKER; + case FOX_SPAWN_EGG: + return EntityType.FOX; + case GHAST_SPAWN_EGG: + return EntityType.GHAST; + case GLOW_SQUID_SPAWN_EGG: + return EntityType.GLOW_SQUID; + case GOAT_SPAWN_EGG: + return EntityType.GOAT; + case GUARDIAN_SPAWN_EGG: + return EntityType.GUARDIAN; + case HOGLIN_SPAWN_EGG: + return EntityType.HOGLIN; + case HORSE_SPAWN_EGG: + return EntityType.HORSE; + case HUSK_SPAWN_EGG: + return EntityType.HUSK; + case LLAMA_SPAWN_EGG: + return EntityType.LLAMA; + case MAGMA_CUBE_SPAWN_EGG: + return EntityType.MAGMA_CUBE; + case MOOSHROOM_SPAWN_EGG: + return EntityType.MUSHROOM_COW; + case MULE_SPAWN_EGG: + return EntityType.MULE; + case OCELOT_SPAWN_EGG: + return EntityType.OCELOT; + case PANDA_SPAWN_EGG: + return EntityType.PANDA; + case PARROT_SPAWN_EGG: + return EntityType.PARROT; + case PHANTOM_SPAWN_EGG: + return EntityType.PHANTOM; + case PIGLIN_BRUTE_SPAWN_EGG: + return EntityType.PIGLIN_BRUTE; + case PIGLIN_SPAWN_EGG: + return EntityType.PIGLIN; + case PILLAGER_SPAWN_EGG: + return EntityType.PILLAGER; + case POLAR_BEAR_SPAWN_EGG: + return EntityType.POLAR_BEAR; + case PUFFERFISH_SPAWN_EGG: + return EntityType.PUFFERFISH; + case RABBIT_SPAWN_EGG: + return EntityType.RABBIT; + case RAVAGER_SPAWN_EGG: + return EntityType.RAVAGER; + case SALMON_SPAWN_EGG: + return EntityType.SALMON; + case SHEEP_SPAWN_EGG: + return EntityType.SHEEP; + case SHULKER_SPAWN_EGG: + return EntityType.SHULKER; + case SILVERFISH_SPAWN_EGG: + return EntityType.SILVERFISH; + case SKELETON_HORSE_SPAWN_EGG: + return EntityType.SKELETON_HORSE; + case SKELETON_SPAWN_EGG: + return EntityType.SKELETON; + case SLIME_SPAWN_EGG: + return EntityType.SLIME; + case SQUID_SPAWN_EGG: + return EntityType.SQUID; + case STRAY_SPAWN_EGG: + return EntityType.STRAY; + case STRIDER_SPAWN_EGG: + return EntityType.STRIDER; + case TRADER_LLAMA_SPAWN_EGG: + return EntityType.TRADER_LLAMA; + case TROPICAL_FISH_SPAWN_EGG: + return EntityType.TROPICAL_FISH; + case TURTLE_SPAWN_EGG: + return EntityType.TURTLE; + case VEX_SPAWN_EGG: + return EntityType.VEX; + case VILLAGER_SPAWN_EGG: + return EntityType.VILLAGER; + case VINDICATOR_SPAWN_EGG: + return EntityType.VINDICATOR; + case WANDERING_TRADER_SPAWN_EGG: + return EntityType.WANDERING_TRADER; + case WITCH_SPAWN_EGG: + return EntityType.WITCH; + case WITHER_SKELETON_SPAWN_EGG: + return EntityType.WITHER_SKELETON; + case WOLF_SPAWN_EGG: + return EntityType.WOLF; + case ZOMBIE_HORSE_SPAWN_EGG: + return EntityType.ZOMBIE_HORSE; + case ZOMBIFIED_PIGLIN_SPAWN_EGG: + return EntityType.ZOMBIFIED_PIGLIN; + case ZOMBIE_SPAWN_EGG: + return EntityType.ZOMBIE; + case ZOMBIE_VILLAGER_SPAWN_EGG: + return EntityType.ZOMBIE_VILLAGER; + case PIG_SPAWN_EGG: + default: // Uhh + return EntityType.PIG; + } + } + + public static boolean isBed(Material material) { + return Tag.BEDS.isTagged(material); + } + + public static boolean isAnvil(Material material) { + return Tag.ANVIL.isTagged(material); + } + + public static boolean isCoral(Material material) { + return Tag.CORAL_BLOCKS.isTagged(material) || + Tag.CORAL_PLANTS.isTagged(material) || + Tag.CORALS.isTagged(material) || + Tag.WALL_CORALS.isTagged(material); + } + + /** + * Test whether the material is a crop. + * @param type the material + * @return true if the material is a crop + */ + public static boolean isCrop(Material type) { + switch (type) { + case WHEAT: + case CARROTS: + case POTATOES: + case BEETROOTS: + case MELON_STEM: + case PUMPKIN_STEM: + case PUMPKIN: + case MELON: + case CACTUS: + case SUGAR_CANE: + case BAMBOO: + case BAMBOO_SAPLING: + case SWEET_BERRY_BUSH: + case NETHER_WART: + case CAVE_VINES: + case CAVE_VINES_PLANT: + return true; + } + return false; + } + + /** + * Test whether the material should be handled as vine. Used by the vine-growth flag + * @param newType the material + * @return true if the material should be handled as vine + */ + public static boolean isVine(Material newType) { + return newType == Material.VINE || + newType == Material.KELP || + newType == Material.TWISTING_VINES || + newType == Material.WEEPING_VINES || + Tag.CAVE_VINES.isTagged(newType); + + } + + /** + * Test whether the given material is affected by + * {@link Flags#USE}. + * + *

Generally, materials that are considered by this method are those + * that are not inventories but can be used.

+ * + * @param material the material + * @return true if covered by the use flag + */ + public static boolean isUseFlagApplicable(Material material) { + if (Tag.BUTTONS.isTagged(material) + || Tag.WOODEN_DOORS.isTagged(material) + || Tag.WOODEN_TRAPDOORS.isTagged(material) + || Tag.FENCE_GATES.isTagged(material) + || Tag.PRESSURE_PLATES.isTagged(material)) { + return true; + } + switch (material) { + case LEVER: + case LECTERN: + case ENCHANTING_TABLE: + case BELL: + case LOOM: + case CARTOGRAPHY_TABLE: + case STONECUTTER: + case GRINDSTONE: + return true; + default: return false; + } + } + + /** + * Test whether the given material is a block that is modified when it is + * left or right clicked. + * + *

This test is conservative, returning true for blocks that it is not + * aware of.

+ * + * @param material the material + * @param rightClick whether it is a right click + * @return true if the block is modified + */ + public static boolean isBlockModifiedOnClick(Material material, boolean rightClick) { + Integer flags = MATERIAL_FLAGS.get(material); + return flags == null + || (rightClick && (flags & MODIFIED_ON_RIGHT) == MODIFIED_ON_RIGHT) + || (!rightClick && (flags & MODIFIED_ON_LEFT) == MODIFIED_ON_LEFT); + } + + /** + * Test whether the given item modifies a given block when right clicked. + * + *

This test is conservative, returning true for items that it is not + * aware of or does not have the details for.

+ * + * @param item the item + * @param block the block + * @return true if the item is applied to the block + */ + public static boolean isItemAppliedToBlock(Material item, Material block) { + Integer flags = MATERIAL_FLAGS.get(item); + return flags == null || (flags & MODIFIES_BLOCKS) == MODIFIES_BLOCKS || isToolApplicable(item, block); + } + + /** + * Test whether the given material should be tested as "building" when + * it is used. + * + * @param type the type + * @return true to be considered as used + */ + public static boolean isConsideredBuildingIfUsed(Material type) { + return type == Material.REPEATER + || type == Material.COMPARATOR + || Tag.FLOWER_POTS.isTagged(type); + } + + /** + * Test whether a list of potion effects contains one or more potion + * effects used for doing damage. + * + * @param effects A collection of effects + * @return True if at least one damage effect exists + */ + public static boolean hasDamageEffect(Collection effects) { + for (PotionEffect effect : effects) { + if (DAMAGE_EFFECTS.contains(effect.getType())) { + return true; + } + } + + return false; + } + + // should match instances of ItemArmor + + /** + * Check if the material is equippable armor (i.e. that it is equipped on right-click + * not necessarily that it can be put in the armor slots) + * + * @param type material to check + * @return true if equippable armor + */ + public static boolean isArmor(Material type) { + switch (type) { + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + case CHAINMAIL_HELMET: + case CHAINMAIL_CHESTPLATE: + case CHAINMAIL_LEGGINGS: + case CHAINMAIL_BOOTS: + case IRON_HELMET: + case IRON_CHESTPLATE: + case IRON_LEGGINGS: + case IRON_BOOTS: + case DIAMOND_HELMET: + case DIAMOND_CHESTPLATE: + case DIAMOND_LEGGINGS: + case DIAMOND_BOOTS: + case GOLDEN_HELMET: + case GOLDEN_CHESTPLATE: + case GOLDEN_LEGGINGS: + case GOLDEN_BOOTS: + case NETHERITE_HELMET: + case NETHERITE_CHESTPLATE: + case NETHERITE_LEGGINGS: + case NETHERITE_BOOTS: + case TURTLE_HELMET: + case ELYTRA: + return true; + default: + return false; + } + } + + /** + * Check if the material is usable via right-click on the target + * material. Returns false if the target material cannot be modified + * by the provided tool, or of the provided tool material isn't + * a tool material. + * + * @param toolMaterial the tool material being used + * @param targetMaterial the target material to check + * @return true if tool has an interact function with this material + */ + public static boolean isToolApplicable(Material toolMaterial, Material targetMaterial) { + switch (toolMaterial) { + case WOODEN_HOE: + case STONE_HOE: + case IRON_HOE: + case GOLDEN_HOE: + case DIAMOND_HOE: + case NETHERITE_HOE: + switch (targetMaterial) { + case GRASS_BLOCK: + case DIRT: + case DIRT_PATH: + case ROOTED_DIRT: + // case COARSE_DIRT: // already handled by the server... + return true; + } + return false; + case WOODEN_AXE: + case STONE_AXE: + case IRON_AXE: + case GOLDEN_AXE: + case DIAMOND_AXE: + case NETHERITE_AXE: + if (isWaxedCopper(targetMaterial)) + return true; + switch (targetMaterial) { + case OAK_LOG: + case DARK_OAK_LOG: + case ACACIA_LOG: + case BIRCH_LOG: + case SPRUCE_LOG: + case JUNGLE_LOG: + case OAK_WOOD: + case DARK_OAK_WOOD: + case ACACIA_WOOD: + case BIRCH_WOOD: + case SPRUCE_WOOD: + case JUNGLE_WOOD: + case CRIMSON_STEM: + case WARPED_STEM: + case CRIMSON_HYPHAE: + case WARPED_HYPHAE: + return true; + } + return false; + case WOODEN_SHOVEL: + case STONE_SHOVEL: + case IRON_SHOVEL: + case GOLDEN_SHOVEL: + case DIAMOND_SHOVEL: + case NETHERITE_SHOVEL: + switch (targetMaterial) { + case GRASS_BLOCK: + case CAMPFIRE: + case SOUL_CAMPFIRE: + return true; + } + return false; + case SHEARS: + switch (targetMaterial) { + case PUMPKIN: + case BEE_NEST: + case BEEHIVE: + return true; + } + return false; + case BLACK_DYE: + case BLUE_DYE: + case BROWN_DYE: + case CYAN_DYE: + case GRAY_DYE: + case GREEN_DYE: + case LIGHT_BLUE_DYE: + case LIGHT_GRAY_DYE: + case LIME_DYE: + case MAGENTA_DYE: + case ORANGE_DYE: + case PINK_DYE: + case PURPLE_DYE: + case RED_DYE: + case WHITE_DYE: + case YELLOW_DYE: + case GLOW_INK_SAC: + case INK_SAC: + return Tag.SIGNS.isTagged(targetMaterial); + case HONEYCOMB: + return isUnwaxedCopper(targetMaterial); + default: + return false; + } + } + + public static boolean isFire(Material type) { + return type == Material.FIRE || type == Material.SOUL_FIRE; + } + + public static boolean isWaxedCopper(Material type) { + switch (type) { + case WAXED_COPPER_BLOCK: + case WAXED_EXPOSED_COPPER: + case WAXED_WEATHERED_COPPER: + case WAXED_OXIDIZED_COPPER: + case WAXED_CUT_COPPER: + case WAXED_EXPOSED_CUT_COPPER: + case WAXED_WEATHERED_CUT_COPPER: + case WAXED_OXIDIZED_CUT_COPPER: + case WAXED_CUT_COPPER_STAIRS: + case WAXED_EXPOSED_CUT_COPPER_STAIRS: + case WAXED_WEATHERED_CUT_COPPER_STAIRS: + case WAXED_OXIDIZED_CUT_COPPER_STAIRS: + case WAXED_CUT_COPPER_SLAB: + case WAXED_EXPOSED_CUT_COPPER_SLAB: + case WAXED_WEATHERED_CUT_COPPER_SLAB: + case WAXED_OXIDIZED_CUT_COPPER_SLAB: + return true; + } + return false; + } + + public static boolean isUnwaxedCopper(Material type) { + switch (type) { + case COPPER_BLOCK: + case EXPOSED_COPPER: + case WEATHERED_COPPER: + case OXIDIZED_COPPER: + case CUT_COPPER: + case EXPOSED_CUT_COPPER: + case WEATHERED_CUT_COPPER: + case OXIDIZED_CUT_COPPER: + case CUT_COPPER_STAIRS: + case EXPOSED_CUT_COPPER_STAIRS: + case WEATHERED_CUT_COPPER_STAIRS: + case OXIDIZED_CUT_COPPER_STAIRS: + case CUT_COPPER_SLAB: + case EXPOSED_CUT_COPPER_SLAB: + case WEATHERED_CUT_COPPER_SLAB: + case OXIDIZED_CUT_COPPER_SLAB: + return true; + } + return false; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/logging/ClassSourceValidator.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/logging/ClassSourceValidator.java new file mode 100644 index 000000000..b6e71d40c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/logging/ClassSourceValidator.java @@ -0,0 +1,127 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.logging; + +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import org.bukkit.plugin.Plugin; + +import javax.annotation.Nullable; +import java.security.CodeSource; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Validates that certain specified classes came from the same source as + * a plugin. + */ +public class ClassSourceValidator { + + private static final Logger log = Logger.getLogger(ClassSourceValidator.class.getCanonicalName()); + private static final String separatorLine = Strings.repeat("*", 46); + + private final Plugin plugin; + @Nullable + private final CodeSource expectedCodeSource; + + /** + * Create a new instance. + * + * @param plugin The plugin + */ + public ClassSourceValidator(Plugin plugin) { + checkNotNull(plugin, "plugin"); + this.plugin = plugin; + this.expectedCodeSource = plugin.getClass().getProtectionDomain().getCodeSource(); + } + + /** + * Return a map of classes that been loaded from a different source. + * + * @param classes A list of classes to check + * @return The results + */ + public Map, CodeSource> findMismatches(List> classes) { + checkNotNull(classes, "classes"); + + Map, CodeSource> mismatches = Maps.newHashMap(); + + if (expectedCodeSource != null) { + for (Class testClass : classes) { + CodeSource testSource = testClass.getProtectionDomain().getCodeSource(); + if (!expectedCodeSource.equals(testSource)) { + mismatches.put(testClass, testSource); + } + } + } + + return mismatches; + } + + /** + * Reports classes that have come from a different source. + * + *

The warning is emitted to the log.

+ * + * @param classes The list of classes to check + */ + public void reportMismatches(List> classes) { + if (Boolean.getBoolean("worldguard.disable.class.validation")) { + return; + } + Map, CodeSource> mismatches = findMismatches(classes); + + if (!mismatches.isEmpty()) { + StringBuilder builder = new StringBuilder("\n"); + + builder.append(separatorLine).append("\n"); + builder.append("** /!\\ SEVERE WARNING /!\\\n"); + builder.append("** \n"); + builder.append("** A plugin developer has copied and pasted a portion of \n"); + builder.append("** ").append(plugin.getName()).append(" into their own plugin, so rather than using\n"); + builder.append("** the version of ").append(plugin.getName()).append(" that you downloaded, you\n"); + builder.append("** will be using a broken mix of old ").append(plugin.getName()).append(" (that came\n"); + builder.append("** with the plugin) and your downloaded version. THIS MAY\n"); + builder.append("** SEVERELY BREAK ").append(plugin.getName().toUpperCase()).append(" AND ALL OF ITS FEATURES.\n"); + builder.append("**\n"); + builder.append("** This may have happened because the developer is using\n"); + builder.append("** the ").append(plugin.getName()).append(" API and thinks that including\n"); + builder.append("** ").append(plugin.getName()).append(" is necessary. However, it is not!\n"); + builder.append("**\n"); + builder.append("** Here are some files that have been overridden:\n"); + builder.append("** \n"); + for (Map.Entry, CodeSource> entry : mismatches.entrySet()) { + CodeSource codeSource = entry.getValue(); + String url = codeSource != null ? codeSource.getLocation().toExternalForm() : "(unknown)"; + builder.append("** '").append(entry.getKey().getSimpleName()).append("' came from '").append(url).append("'\n"); + } + builder.append("**\n"); + builder.append("** Please report this to the plugins' developers.\n"); + builder.append(separatorLine).append("\n"); + + log.log(Level.SEVERE, builder.toString()); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/CancelReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/CancelReport.java new file mode 100644 index 000000000..e11c52a5e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/CancelReport.java @@ -0,0 +1,125 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.sk89q.worldguard.bukkit.event.debug.CancelAttempt; +import com.sk89q.worldguard.bukkit.util.HandlerTracer; +import com.sk89q.worldedit.util.report.Report; +import com.sk89q.worldedit.util.report.StackTraceReport; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.plugin.Plugin; + +import java.util.Arrays; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Reports on cancelled events. + */ +public class CancelReport implements Report { + + private final Event event; + private final Cancellable cancellable; + private final List cancels; + private final HandlerTracer tracer; + private final int stackTruncateLength; + private boolean detectingPlugin = true; + + public CancelReport(T event, List cancels, int stackTruncateLength) { + checkNotNull(event, "event"); + checkNotNull(cancels, "cancels"); + this.event = event; + this.cancellable = event; + this.cancels = cancels; + this.tracer = new HandlerTracer(event); + this.stackTruncateLength = stackTruncateLength; + } + + public boolean isDetectingPlugin() { + return detectingPlugin; + } + + public void setDetectingPlugin(boolean detectingPlugin) { + this.detectingPlugin = detectingPlugin; + } + + private StackTraceElement[] truncateStackTrace(StackTraceElement[] elements) { + int newLength = elements.length - stackTruncateLength; + if (newLength <= 0) { + return new StackTraceElement[0]; + } else { + return Arrays.copyOf(elements, newLength); + } + } + + @Override + public String getTitle() { + return null; + } + + @Override + public String toString() { + if (!cancels.isEmpty()) { + StringBuilder builder = new StringBuilder(); + + builder.append("Was the action blocked? ").append(cancellable.isCancelled() ? "YES" : "NO").append("\n"); + + if (cancels.size() != 1) { + builder.append("Entry #1 had the last word.\n"); + } + + for (int i = cancels.size() - 1; i >= 0; i--) { + CancelAttempt cancel = cancels.get(i); + int index = cancels.size() - i; + + StackTraceElement[] stackTrace = truncateStackTrace(cancel.getStackTrace()); + Plugin cause = tracer.detectPlugin(stackTrace); + + builder.append("#").append(index).append(" "); + builder.append(getCancelText(cancel.getAfter())); + builder.append(" by "); + + if (detectingPlugin && cause != null) { + builder.append(cause.getName()); + } else { + builder.append(" (NOT KNOWN - use the stack trace below)"); + builder.append("\n"); + builder.append(new StackTraceReport(stackTrace).toString().replaceAll("(?m)^", "\t")); + } + + builder.append("\n"); + } + + return builder.toString(); + } else { + return "No plugins cancelled the event. Other causes for cancellation: " + + "(1) Bukkit may be using a different event for the action " + + " (example: buckets have their own bucket events); or " + + "(2) Minecraft's spawn protection has not been disabled."; + } + } + + private static String getCancelText(boolean flag) { + return flag ? "BLOCKED" : "ALLOWED"; + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PerformanceReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PerformanceReport.java new file mode 100644 index 000000000..736bcb0c5 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PerformanceReport.java @@ -0,0 +1,103 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Entity; + +import java.util.List; +import java.util.Map; + +public class PerformanceReport extends DataReport { + + public PerformanceReport() { + super("Performance"); + + List worlds = Bukkit.getServer().getWorlds(); + + append("World Count", worlds.size()); + + for (World world : worlds) { + int loadedChunkCount = world.getLoadedChunks().length; + + DataReport report = new DataReport("World: " + world.getName()); + report.append("Keep in Memory?", world.getKeepSpawnInMemory()); + report.append("Entity Count", world.getEntities().size()); + report.append("Chunk Count", loadedChunkCount); + + Map, Integer> entityCounts = Maps.newHashMap(); + Map, Integer> tileEntityCounts = Maps.newHashMap(); + + // Collect tile entities + int teCount = 0; + for (Chunk chunk : world.getLoadedChunks()) { + teCount += chunk.getTileEntities().length; + for (BlockState state : chunk.getTileEntities()) { + Class cls = state.getClass(); + + if (tileEntityCounts.containsKey(cls)) { + tileEntityCounts.put(cls, tileEntityCounts.get(cls) + 1); + } else { + tileEntityCounts.put(cls, 1); + } + } + } + report.append("Tile Entity Count", teCount); + + // Collect entities + for (Entity entity : world.getEntities()) { + Class cls = entity.getClass(); + + if (entityCounts.containsKey(cls)) { + entityCounts.put(cls, entityCounts.get(cls) + 1); + } else { + entityCounts.put(cls, 1); + } + } + + // Print entities + DataReport entities = new DataReport("Entity Distribution"); + for (Map.Entry, Integer> entry : entityCounts.entrySet()) { + entities.append(entry.getKey().getSimpleName(), "%d [%f/chunk]", + entry.getValue(), + (float) (entry.getValue() / (double) loadedChunkCount)); + } + report.append(entities.getTitle(), entities); + + // Print tile entities + DataReport tileEntities = new DataReport("Tile Entity Distribution"); + for (Map.Entry, Integer> entry : tileEntityCounts.entrySet()) { + tileEntities.append(entry.getKey().getSimpleName(), "%d [%f/chunk]", + entry.getValue(), + (float) (entry.getValue() / (double) loadedChunkCount)); + } + report.append(tileEntities.getTitle(), tileEntities); + + append(report.getTitle(), report); + } + + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PluginReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PluginReport.java new file mode 100644 index 000000000..e3e5e747e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/PluginReport.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +public class PluginReport extends DataReport { + + public PluginReport() { + super("Plugins"); + + Plugin[] plugins = Bukkit.getServer().getPluginManager().getPlugins(); + + append("Plugin Count", plugins.length); + + for (Plugin plugin : plugins) { + DataReport report = new DataReport("Plugin: " + plugin.getName()); + report.append("Enabled?", plugin.isEnabled()); + report.append("Full Name", plugin.getDescription().getFullName()); + report.append("Version", plugin.getDescription().getVersion()); + report.append("Website", plugin.getDescription().getWebsite()); + report.append("Description", plugin.getDescription().getDescription()); + report.append("Authors", plugin.getDescription().getAuthors()); + report.append("Load Before", plugin.getDescription().getLoadBefore()); + report.append("Dependencies", plugin.getDescription().getDepend()); + report.append("Soft Dependencies", plugin.getDescription().getSoftDepend()); + report.append("Folder", plugin.getDataFolder().getAbsoluteFile()); + report.append("Entry Point", plugin.getDescription().getMain()); + report.append("Class", plugin.getClass().getName()); + report.append("Class Source", plugin.getClass().getProtectionDomain().getCodeSource().getLocation()); + append(report.getTitle(), report); + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/SchedulerReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/SchedulerReport.java new file mode 100644 index 000000000..d897162cf --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/SchedulerReport.java @@ -0,0 +1,89 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.google.common.reflect.TypeToken; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitTask; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class SchedulerReport extends DataReport { + + private LoadingCache, Optional> taskFieldCache = CacheBuilder.newBuilder() + .build(new CacheLoader, Optional>() { + @Override + public Optional load(Class clazz) throws Exception { + try { + Field field = clazz.getDeclaredField("task"); + field.setAccessible(true); + return Optional.ofNullable(field); + } catch (NoSuchFieldException ignored) { + return Optional.empty(); + } + } + }); + + public SchedulerReport() { + super("Scheduler"); + + List tasks = Bukkit.getServer().getScheduler().getPendingTasks(); + + append("Pending Task Count", tasks.size()); + + for (BukkitTask task : tasks) { + Class taskClass = getTaskClass(task); + + DataReport report = new DataReport("Task: #" + task.getTaskId()); + report.append("Owner", task.getOwner().getName()); + report.append("Runnable", taskClass != null ? taskClass.getName() : ""); + report.append("Synchronous?", task.isSync()); + append(report.getTitle(), report); + } + } + + @SuppressWarnings("unchecked") + @Nullable + private Class getTaskClass(BukkitTask task) { + try { + Class clazz = task.getClass(); + Set> classes = (Set) TypeToken.of(clazz).getTypes().rawTypes(); + + for (Class type : classes) { + Optional field = taskFieldCache.getUnchecked(type); + if (field.isPresent()) { + Object res = field.get().get(task); + return res == null ? null : res.getClass(); + } + } + } catch (IllegalAccessException | NoClassDefFoundError ignored) { + } + + return null; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServerReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServerReport.java new file mode 100644 index 000000000..a9be6539e --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServerReport.java @@ -0,0 +1,65 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.Server; + +public class ServerReport extends DataReport { + + public ServerReport() { + super("Server Information"); + + Server server = Bukkit.getServer(); + + append("Bukkit Version", server.getBukkitVersion()); + append("Implementation", server.getName() + " " + server.getVersion()); + append("Player Count", "%d/%d", Bukkit.getOnlinePlayers().size(), server.getMaxPlayers()); + + append("Server Class Source", server.getClass().getProtectionDomain().getCodeSource().getLocation()); + + DataReport spawning = new DataReport("Spawning"); + spawning.append("Ambient Spawn Limit", server.getAmbientSpawnLimit()); + spawning.append("Animal Spawn Limit", server.getAnimalSpawnLimit()); + spawning.append("Monster Spawn Limit", server.getMonsterSpawnLimit()); + spawning.append("Ticks per Animal Spawn", server.getTicksPerAnimalSpawns()); + spawning.append("Ticks per Monster Spawn", server.getTicksPerMonsterSpawns()); + append(spawning.getTitle(), spawning); + + DataReport config = new DataReport("Configuration"); + config.append("Nether Enabled?", server.getAllowNether()); + config.append("The End Enabled?", server.getAllowEnd()); + config.append("Generate Structures?", server.getGenerateStructures()); + config.append("Flight Allowed?", server.getAllowFlight()); + config.append("Connection Throttle", server.getConnectionThrottle()); + config.append("Idle Timeout", server.getIdleTimeout()); + config.append("Shutdown Message", server.getShutdownMessage()); + config.append("Default Game Mode", server.getDefaultGameMode()); + config.append("Main World Type", server.getWorldType()); + config.append("View Distance", server.getViewDistance()); + append(config.getTitle(), config); + + DataReport protection = new DataReport("Protection"); + protection.append("Spawn Radius", server.getSpawnRadius()); + append(protection.getTitle(), protection); + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServicesReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServicesReport.java new file mode 100644 index 000000000..09322bf67 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/ServicesReport.java @@ -0,0 +1,44 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.plugin.ServicesManager; + +import java.util.Collection; + +public class ServicesReport extends DataReport { + + public ServicesReport() { + super("Services"); + + ServicesManager manager = Bukkit.getServer().getServicesManager(); + Collection> services = manager.getKnownServices(); + + for (Class service : services) { + Object provider = manager.load(service); + if (provider != null) { + append(service.getName(), provider); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/WorldReport.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/WorldReport.java new file mode 100644 index 000000000..a4e45e6ad --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/bukkit/util/report/WorldReport.java @@ -0,0 +1,80 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.bukkit.util.report; + +import com.sk89q.worldedit.util.report.DataReport; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.generator.ChunkGenerator; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class WorldReport extends DataReport { + + public WorldReport() { + super("Worlds"); + + List worlds = Bukkit.getServer().getWorlds(); + + append("World Count", worlds.size()); + + for (World world : worlds) { + DataReport report = new DataReport("World: " + world.getName()); + report.append("UUID", world.getUID()); + report.append("World Type", world.getWorldType()); + report.append("Environment", world.getEnvironment()); + ChunkGenerator generator = world.getGenerator(); + report.append("Chunk Generator", generator != null ? generator.getClass().getName() : ""); + + DataReport spawning = new DataReport("Spawning"); + spawning.append("Animals?", world.getAllowAnimals()); + spawning.append("Monsters?", world.getAllowMonsters()); + spawning.append("Ambient Spawn Limit", world.getAmbientSpawnLimit()); + spawning.append("Animal Spawn Limit", world.getAnimalSpawnLimit()); + spawning.append("Monster Spawn Limit", world.getMonsterSpawnLimit()); + spawning.append("Water Creature Spawn Limit", world.getWaterAnimalSpawnLimit()); + report.append(spawning.getTitle(), spawning); + + DataReport config = new DataReport("Configuration"); + config.append("Difficulty", world.getDifficulty()); + config.append("Max Height", world.getMaxHeight()); + config.append("Sea Level", world.getSeaLevel()); + report.append(config.getTitle(), config); + + DataReport state = new DataReport("State"); + state.append("Spawn Location", world.getSpawnLocation()); + state.append("Full Time", world.getFullTime()); + state.append("Weather Duration", world.getWeatherDuration()); + state.append("Thunder Duration", world.getThunderDuration()); + report.append(state.getTitle(), state); + + DataReport protection = new DataReport("Protection"); + protection.append("PVP?", world.getPVP()); + protection.append("Game Rules", Arrays.stream(world.getGameRules()) + .map(name -> name + "=" + world.getGameRuleValue(name)) + .collect(Collectors.joining(", "))); + report.append(protection.getTitle(), protection); + + append(report.getTitle(), report); + } + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/util/profile/resolver/PaperProfileService.java b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/util/profile/resolver/PaperProfileService.java new file mode 100644 index 000000000..c65e8d028 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/java/com/sk89q/worldguard/util/profile/resolver/PaperProfileService.java @@ -0,0 +1,63 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profile.resolver; + +import com.sk89q.worldguard.util.profile.Profile; +import io.papermc.lib.PaperLib; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.UUID; + +/** + * @deprecated Use {@link com.sk89q.worldguard.util.profile.resolver.PaperPlayerService} instead + */ +@Deprecated(forRemoval = true) +public final class PaperProfileService extends SingleRequestService { + private static final PaperPlayerService SUPER = PaperPlayerService.getInstance(); + private static final PaperProfileService INSTANCE = new PaperProfileService(); + + private PaperProfileService() { + if (!PaperLib.isPaper()) { + throw new IllegalStateException("Attempt to access PaperProfileService on non-Paper server."); + } + } + + @Override + public int getIdealRequestLimit() { + return SUPER.getIdealRequestLimit(); + } + + @Override + @Nullable + public Profile findByName(String name) throws IOException, InterruptedException { + return SUPER.findByName(name); + } + + @Nullable + @Override + public Profile findByUuid(UUID uuid) throws IOException, InterruptedException { + return SUPER.findByUuid(uuid); + } + + public static PaperProfileService getInstance() { + return INSTANCE; + } +} diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/blacklist.txt b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/blacklist.txt new file mode 100644 index 000000000..ae7f7dae1 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/blacklist.txt @@ -0,0 +1,62 @@ +# +# WorldGuard blacklist +# +# The blacklist lets you block actions, blocks, and items from being used. +# You choose a set of "items to affect" and a list of "actions to perform." +# +############################################################################### +# +# Example to block some ore mining and placement: +# [coal_ore,gold_ore,iron_ore] +# on-break=deny,log,kick +# on-place=deny,tell +# +# Events that you can detect: +# - on-break (when a block of this type is about to be broken) +# - on-destroy-with (the item/block held by the user while destroying) +# - on-place (a block is being placed) +# - on-use (an item like flint and steel or a bucket is being used) +# - on-interact (when a block in used (doors, chests, etc.)) +# - on-drop (an item is being dropped from the player's inventory) +# - on-acquire (an item enters a player's inventory via some method) +# - on-equip (an item is equipped to the player's armor slots) +# NOTE: on-equip is overprotective due to deficiencies in Bukkit API +# - on-dispense (a dispenser is about to dispense an item) +# +# Actions (for events): +# - deny (deny completely, used blacklist mode) +# - allow (used in whitelist mode) +# - notify (notify admins with the 'worldguard.notify' permission) +# - log (log to console/file/database) +# - tell (tell a player that that's not allowed) +# - kick (kick player) +# - ban (ban player) +# +# Options: +# - ignore-groups (comma-separated list of groups to not affect) +# - ignore-perms (comma-separated list of permissions to not affect - make up +# your very own permissions!) +# - comment (message for yourself that is printed with 'log' and 'notify') +# - message (optional message to show the user instead; %s is the item name) +# +############################################################################### +# +# For more information, see: +# https://worldguard.enginehub.org/en/latest/blacklist/ +# +############################################################################### +# +# Some examples follow. +# REMEMBER: If a line has # in front, it will be ignored. +# + +# Deny lava buckets +#[lava_bucket] +#ignore-perms=my.own.madeup.permission +#ignore-groups=admins,mods +#on-use=deny,tell + +# Deny some ore +#[coal_ore,gold_ore,iron_ore] +#ignore-groups=admins,mods +#on-break=notify,deny,log diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config.yml b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config.yml new file mode 100644 index 000000000..4501e6fb8 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config.yml @@ -0,0 +1,22 @@ +# +# WorldGuard's configuration file +# +# About editing this file: +# - DO NOT USE TABS. You MUST use spaces or Bukkit will complain. If +# you use an editor like Notepad++ (recommended for Windows users), you +# must configure it to "replace tabs with spaces." In Notepad++, this can +# be changed in Settings > Preferences > Language Menu. +# - Don't get rid of the indents. They are indented so some entries are +# in categories (like "enforce-single-session" is in the "protection" +# category. +# - If you want to check the format of this file before putting it +# into WorldGuard, paste it into http://yaml-online-parser.appspot.com/ +# and see if it gives "ERROR:". +# - Lines starting with # are commentsand so they are ignored. +# +# WARNING: +# Remember to check the compatibility spreadsheet for WorldGuard to see +# if any features are currently broken in your version of Bukkit. +# + +# -- This should be automatically replaced by the plugin in-game -- \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config_world.yml b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config_world.yml new file mode 100644 index 000000000..db29fa3ad --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/defaults/config_world.yml @@ -0,0 +1,21 @@ +# +# WorldGuard's configuration file. +# +# This is the a per-world configuration file. It only affects one +# corresponding world. +# +# About editing this file: +# - DO NOT USE TABS. You MUST use spaces or Bukkit will complain. If +# you use an editor like Notepad++ (recommended for Windows users), you +# must configure it to "replace tabs with spaces." In Notepad++, this can +# be changed in Settings > Preferences > Language Menu. +# - Don't get rid of the indents. They are indented so some entries are +# in categories (like "enforce-single-session" is in the "protection" +# category. +# - If you want to check the format of this file before putting it +# into WorldGuard, paste it into http://yaml-online-parser.appspot.com/ +# and see if it gives "ERROR:". +# - Lines starting with # are comments and so they are ignored. +# + +# -- This should be automatically replaced by the plugin in-game -- \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V1__Initial.sql b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V1__Initial.sql new file mode 100644 index 000000000..a04964f6c --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V1__Initial.sql @@ -0,0 +1,212 @@ +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; + +-- ----------------------------------------------------- +-- Table `group` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}group` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT , + `name` VARCHAR(64) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + PRIMARY KEY (`id`) , + UNIQUE INDEX `name` (`name` ASC) ) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `world` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}world` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT , + `name` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + PRIMARY KEY (`id`) , + UNIQUE INDEX `name` (`name` ASC) ) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region` ( + `id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `type` ENUM('cuboid','poly2d','global') CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `priority` SMALLINT(6) NOT NULL DEFAULT '0' , + `parent` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL , + PRIMARY KEY (`id`, `world_id`) , + INDEX `fk_region_world` (`world_id` ASC) , + INDEX `parent` (`parent` ASC) , + CONSTRAINT `fk_${tablePrefix}region_world1` + FOREIGN KEY (`world_id` ) + REFERENCES `${tablePrefix}world` (`id` ) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT `${tablePrefix}parent` + FOREIGN KEY (`parent` ) + REFERENCES `${tablePrefix}region` (`id` ) + ON DELETE SET NULL + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_cuboid` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_cuboid` ( + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `min_x` BIGINT(20) NOT NULL , + `min_y` BIGINT(20) NOT NULL , + `min_z` BIGINT(20) NOT NULL , + `max_x` BIGINT(20) NOT NULL , + `max_y` BIGINT(20) NOT NULL , + `max_z` BIGINT(20) NOT NULL , + PRIMARY KEY (`region_id`, `world_id`) , + INDEX `fk_region_cuboid_region` (`region_id` ASC) , + CONSTRAINT `fk_${tablePrefix}region_cuboid_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_flag` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_flag` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT , + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `flag` VARCHAR(45) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `value` VARCHAR(512) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + PRIMARY KEY (`id`) , + INDEX `fk_flags_region` (`region_id` ASC, `world_id` ASC) , + CONSTRAINT `fk_${tablePrefix}flags_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_groups` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_groups` ( + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `group_id` INT(10) UNSIGNED NOT NULL , + `owner` TINYINT(1) NOT NULL , + PRIMARY KEY (`region_id`, `world_id`, `group_id`) , + INDEX `fk_region_groups_region` (`region_id` ASC) , + INDEX `fk_region_groups_group` (`group_id` ASC) , + CONSTRAINT `fk_${tablePrefix}region_groups_group` + FOREIGN KEY (`group_id` ) + REFERENCES `${tablePrefix}group` (`id` ) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT `fk_${tablePrefix}region_groups_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `user` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}user` ( + `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT , + `name` VARCHAR(64) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + PRIMARY KEY (`id`) , + UNIQUE INDEX `name` (`name` ASC) ) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_players` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_players` ( + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `user_id` INT(10) UNSIGNED NOT NULL , + `owner` TINYINT(1) NOT NULL , + PRIMARY KEY (`region_id`, `world_id`, `user_id`) , + INDEX `fk_region_players_region` (`region_id` ASC) , + INDEX `fk_region_users_user` (`user_id` ASC) , + CONSTRAINT `fk_${tablePrefix}region_users_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT `fk_${tablePrefix}region_users_user` + FOREIGN KEY (`user_id` ) + REFERENCES `${tablePrefix}user` (`id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_poly2d` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_poly2d` ( + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `min_y` INT(11) NOT NULL , + `max_y` INT(11) NOT NULL , + PRIMARY KEY (`region_id`, `world_id`) , + INDEX `fk_region_poly2d_region` (`region_id` ASC) , + CONSTRAINT `fk_${tablePrefix}region_poly2d_region` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region` (`id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + +-- ----------------------------------------------------- +-- Table `region_poly2d_point` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `${tablePrefix}region_poly2d_point` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT , + `region_id` VARCHAR(128) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL , + `world_id` INT(10) UNSIGNED NOT NULL , + `x` BIGINT(20) NOT NULL , + `z` BIGINT(20) NOT NULL , + PRIMARY KEY (`id`) , + INDEX `fk_region_poly2d_point_region_poly2d` (`region_id` ASC, `world_id` ASC) , + CONSTRAINT `fk_${tablePrefix}region_poly2d_point_region_poly2d` + FOREIGN KEY (`region_id` , `world_id` ) + REFERENCES `${tablePrefix}region_poly2d` (`region_id` , `world_id` ) + ON DELETE CASCADE + ON UPDATE CASCADE) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8 +COLLATE = utf8_bin; + + + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql new file mode 100644 index 000000000..b1df899f4 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/mysql/V2__Bug_fix_and_UUID.sql @@ -0,0 +1,27 @@ +-- Fix WORLDGUARD-3117 +-- Otherwise, you can't be both an owner and a member of a region + +ALTER TABLE `${tablePrefix}region_players` + DROP PRIMARY KEY, + ADD PRIMARY KEY (`region_id`, `world_id`, `user_id`, `owner`); + +ALTER TABLE `${tablePrefix}region_groups` + DROP PRIMARY KEY, + ADD PRIMARY KEY (`region_id`, `world_id`, `group_id`, `owner`); + +-- Fix WORLDGUARD-3030 +-- Adds UUID support + +ALTER TABLE `${tablePrefix}user` + ALTER `name` DROP DEFAULT; + +ALTER TABLE `${tablePrefix}user` + CHANGE COLUMN `name` `name` VARCHAR(64) NULL COLLATE 'utf8_bin' AFTER `id`, + ADD COLUMN `uuid` CHAR(36) NULL AFTER `name`, + ADD UNIQUE INDEX `uuid` (`uuid`); + +-- Strings with differing numbers of trailing spaces are equal in MySQL +-- The domains have been updated to trim strings + +UPDATE `${tablePrefix}user` SET `name` = TRIM(`name`); +UPDATE `${tablePrefix}group` SET `name` = TRIM(`name`); \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/sqlite/V1__Initial.sql b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/sqlite/V1__Initial.sql new file mode 100644 index 000000000..2b5b99e39 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/migrations/region/sqlite/V1__Initial.sql @@ -0,0 +1,160 @@ + +CREATE TABLE "${tablePrefix}world" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT NOT NULL + UNIQUE +); + + +CREATE TABLE "${tablePrefix}region" ( + id TEXT NOT NULL, + world_id INTEGER NOT NULL + REFERENCES "${tablePrefix}world" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + type TEXT NOT NULL, + priority INTEGER NOT NULL, + parent TEXT DEFAULT ( NULL ) + --REFERENCES "${tablePrefix}region" ( id ) ON DELETE SET NULL + -- ON UPDATE CASCADE -- Not supported + , + PRIMARY KEY ( id, world_id ) +); + + +CREATE TABLE "${tablePrefix}user" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT UNIQUE + DEFAULT ( NULL ), + uuid TEXT UNIQUE + DEFAULT ( NULL ) +); + + +CREATE TABLE "${tablePrefix}group" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + name TEXT NOT NULL + UNIQUE +); + + +CREATE TABLE "${tablePrefix}region_cuboid" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + min_x INTEGER NOT NULL, + min_y INTEGER NOT NULL, + min_z INTEGER NOT NULL, + max_x INTEGER NOT NULL, + max_y INTEGER NOT NULL, + max_z INTEGER NOT NULL, + PRIMARY KEY ( region_id, world_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_poly2d" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + min_y INTEGER NOT NULL, + max_y INTEGER NOT NULL, + PRIMARY KEY ( region_id, world_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_poly2d_point" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + x INTEGER NOT NULL, + z INTEGER NOT NULL, + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region_poly2d" ( region_id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_groups" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + group_id INTEGER NOT NULL + REFERENCES "${tablePrefix}group" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + owner BOOLEAN NOT NULL, + PRIMARY KEY ( region_id, world_id, group_id ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_flag" ( + id INTEGER PRIMARY KEY AUTOINCREMENT + NOT NULL, + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + flag TEXT NOT NULL, + value TEXT NOT NULL, + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE TABLE "${tablePrefix}region_players" ( + region_id TEXT NOT NULL, + world_id INTEGER NOT NULL, + user_id INTEGER NOT NULL + REFERENCES "${tablePrefix}user" ( id ) ON DELETE CASCADE + ON UPDATE CASCADE, + owner BOOLEAN NOT NULL, + PRIMARY KEY ( region_id, world_id, user_id, owner ), + FOREIGN KEY ( region_id, world_id ) REFERENCES "${tablePrefix}region" ( id, world_id ) ON DELETE CASCADE + ON UPDATE CASCADE +); + + +CREATE INDEX "idx_${tablePrefix}region_cuboid_region_id" ON "${tablePrefix}region_cuboid" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_world_id" ON "${tablePrefix}region" ( + world_id +); + + +CREATE INDEX "idx_${tablePrefix}region_parent" ON "${tablePrefix}region" ( + parent +); + + +CREATE INDEX "idx_${tablePrefix}region_poly2d_region_id" ON "${tablePrefix}region_poly2d" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_poly2d_point_region_world_id" ON "${tablePrefix}region_poly2d_point" ( + region_id, + world_id +); + + +CREATE INDEX "idx_${tablePrefix}region_groups_region_id" ON "${tablePrefix}region_groups" ( + region_id +); + + +CREATE INDEX "idx_${tablePrefix}region_groups_group_id" ON "${tablePrefix}region_groups" ( + group_id +); + + +CREATE INDEX "idx_${tablePrefix}region_flag_region_world_id" ON "${tablePrefix}region_flag" ( + region_id, + world_id, + flag +); + diff --git a/sk89q-worldguard/worldguard-bukkit/src/main/resources/plugin.yml b/sk89q-worldguard/worldguard-bukkit/src/main/resources/plugin.yml new file mode 100644 index 000000000..8a30c8b38 --- /dev/null +++ b/sk89q-worldguard/worldguard-bukkit/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +name: WorldGuard +main: com.sk89q.worldguard.bukkit.WorldGuardPlugin +version: "${internalVersion}" +depend: [WorldEdit] +softdepend: [CommandBook] +api-version: 1.17 diff --git a/sk89q-worldguard/worldguard-core/build.gradle.kts b/sk89q-worldguard/worldguard-core/build.gradle.kts new file mode 100644 index 000000000..35e2a22b7 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `java-library` +} + +applyPlatformAndCoreConfiguration() + +dependencies { + "api"(project(":worldguard-libs:core")) + "api"("com.sk89q.worldedit:worldedit-core:${Versions.WORLDEDIT}") + "implementation"("org.flywaydb:flyway-core:3.0") + "implementation"("org.yaml:snakeyaml:1.29") + "implementation"("com.google.guava:guava:${Versions.GUAVA}") + + "compileOnly"("com.google.code.findbugs:jsr305:1.3.9") + "testImplementation"("org.hamcrest:hamcrest-library:1.2.1") +} + +tasks.withType().configureEach { + dependsOn(":worldguard-libs:build") +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java new file mode 100644 index 000000000..7395bebf6 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/LocalPlayer.java @@ -0,0 +1,223 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard; + +import com.google.common.annotations.Beta; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.List; + +public interface LocalPlayer extends Player, RegionAssociable { + + /** + * Returns true if this player is inside a group. + * + * @param group The group to check + * @return Whether this player is in {@code group} + */ + boolean hasGroup(String group); + + /** + * Kick this player. + * + * @param msg The message to kick the player with + */ + void kick(String msg); + + /** + * Ban this player. + * + * @param msg The message to ban the player with + */ + void ban(String msg); + + @Override + default Association getAssociation(List regions) { + boolean member = false; + + for (ProtectedRegion region : regions) { + if (region.isOwner(this)) { + return Association.OWNER; + } else if (!member && region.isMember(this)) { + member = true; + } + } + + return member ? Association.MEMBER : Association.NON_MEMBER; + } + + /** + * Gets the health of this player. + * + * @return The health + */ + double getHealth(); + + /** + * Sets the health of this player. + * + * @param health The health + */ + void setHealth(double health); + + /** + * Gets the max health of this player. + * + * @return The max health + */ + double getMaxHealth(); + + /** + * Gets the food level of this player. + * + * @return The food level + */ + double getFoodLevel(); + + /** + * Sets the food level of this player. + * + * @param foodLevel The food level + */ + void setFoodLevel(double foodLevel); + + /** + * Gets the saturation of this player. + * + * @return The saturation + */ + double getSaturation(); + + /** + * Sets the saturation of this player. + * + * @param saturation The saturation + */ + void setSaturation(double saturation); + + /** + * Gets the exhaustion of this player. + * + * @return The exhaustion + */ + float getExhaustion(); + + /** + * Sets the exhaustion of this player. + * + * @param exhaustion The exhaustion + */ + void setExhaustion(float exhaustion); + + /** + * Gets the players weather + * + * @return The players weather + */ + WeatherType getPlayerWeather(); + + /** + * Sets the players WeatherType + * + * @param weather The weather type + */ + void setPlayerWeather(WeatherType weather); + + /** + * Resets the players weather to normal. + */ + void resetPlayerWeather(); + + /** + * Gets if the players time is relative. + * + * @return If the time is relative + */ + boolean isPlayerTimeRelative(); + + /** + * Gets the time offset of the player. + * + * @return The players time offset + */ + long getPlayerTimeOffset(); + + /** + * Sets the players time. + * + * @param time The players time + * @param relative If it's relative + */ + void setPlayerTime(long time, boolean relative); + + /** + * Resets the players time to normal. + */ + void resetPlayerTime(); + + // TODO Move this to WorldEdit's Entity class - honestly most of this class could be a Facet + /** + * Gets the number of ticks the player is on fire for. + * + * @return The number of fire ticks + */ + int getFireTicks(); + + /** + * Sets the number of ticks the player is on fire for. + * + * @param fireTicks The fire ticks + */ + void setFireTicks(int fireTicks); + + /** + * Sets the target of the compass + * + * @param location The location + */ + void setCompassTarget(Location location); + + /** + * This should preferably take Components but there's no way to do that yet + * + * @param title the title to display + * @param subtitle the subtitle to display + */ + @Beta + void sendTitle(String title, String subtitle); + + /** + * Clears fall distance. + */ + void resetFallDistance(); + + /** + * Teleport the player, potentially async, displaying the message on a success. + * @param location location to teleport to + * @param successMessage message to display on success + * @param failMessage message to display on failure + */ + void teleport(Location location, String successMessage, String failMessage); +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/WorldGuard.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/WorldGuard.java new file mode 100644 index 000000000..637eee7bd --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/WorldGuard.java @@ -0,0 +1,238 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldguard.util.profile.cache.HashMapCache; +import com.sk89q.worldguard.util.profile.cache.ProfileCache; +import com.sk89q.worldguard.util.profile.cache.SQLiteCache; +import com.sk89q.worldguard.util.profile.resolver.ProfileService; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.task.SimpleSupervisor; +import com.sk89q.worldedit.util.task.Supervisor; +import com.sk89q.worldedit.util.task.Task; +import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.flags.registry.SimpleFlagRegistry; +import com.sk89q.worldguard.util.WorldGuardExceptionConverter; +import com.sk89q.worldguard.util.concurrent.EvenMoreExecutors; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public final class WorldGuard { + + public static final Logger logger = Logger.getLogger(WorldGuard.class.getCanonicalName()); + + private static String version; + private static final WorldGuard instance = new WorldGuard(); + + private WorldGuardPlatform platform; + private final SimpleFlagRegistry flagRegistry = new SimpleFlagRegistry(); + private final Supervisor supervisor = new SimpleSupervisor(); + private ProfileCache profileCache; + private ProfileService profileService; + private ListeningExecutorService executorService; + private WorldGuardExceptionConverter exceptionConverter = new WorldGuardExceptionConverter(); + + static { + Flags.registerAll(); + } + + public static WorldGuard getInstance() { + return instance; + } + + private WorldGuard() { + } + + public void setup() { + executorService = MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 1, 20, + "WorldGuard Task Executor - %s")); + + File cacheDir = new File(getPlatform().getConfigDir().toFile(), "cache"); + cacheDir.mkdirs(); + + try { + profileCache = new SQLiteCache(new File(cacheDir, "profiles.sqlite")); + } catch (IOException | UnsatisfiedLinkError ignored) { + logger.log(Level.WARNING, "Failed to initialize SQLite profile cache. Cache is memory-only."); + profileCache = new HashMapCache(); + } + + profileService = getPlatform().createProfileService(profileCache); + + getPlatform().load(); + } + + /** + * The WorldGuard Platform. + * The Platform is only available after WorldGuard is enabled. + * + * @return The platform + */ + public WorldGuardPlatform getPlatform() { + checkNotNull(platform, "WorldGuard is not enabled, unable to access the platform."); + return platform; + } + + public void setPlatform(WorldGuardPlatform platform) { + checkNotNull(platform); + this.platform = platform; + } + + /** + * Get the flag registry. + * + * @return the flag registry + */ + public FlagRegistry getFlagRegistry() { + return this.flagRegistry; + } + + /** + * Get the supervisor. + * + * @return the supervisor + */ + public Supervisor getSupervisor() { + return supervisor; + } + + /** + * Get the global executor service for internal usage (please use your + * own executor service). + * + * @return the global executor service + */ + public ListeningExecutorService getExecutorService() { + return executorService; + } + + /** + * Get the profile lookup service. + * + * @return the profile lookup service + */ + public ProfileService getProfileService() { + return profileService; + } + + /** + * Get the profile cache. + * + * @return the profile cache + */ + public ProfileCache getProfileCache() { + return profileCache; + } + + /** + * Get the exception converter + * + * @return the exception converter + */ + public WorldGuardExceptionConverter getExceptionConverter() { + return exceptionConverter; + } + + /** + * Checks to see if the sender is a player, otherwise throw an exception. + * + * @param sender The sender + * @return The player + * @throws CommandException if it isn't a player + */ + public LocalPlayer checkPlayer(Actor sender) throws CommandException { + if (sender instanceof LocalPlayer) { + return (LocalPlayer) sender; + } else { + throw new CommandException("A player is expected."); + } + } + + /** + * Called when WorldGuard should be disabled. + */ + public void disable() { + executorService.shutdown(); + + try { + logger.log(Level.INFO, "Shutting down executor and cancelling any pending tasks..."); + + List> tasks = supervisor.getTasks(); + if (!tasks.isEmpty()) { + StringBuilder builder = new StringBuilder("Known tasks:"); + for (Task task : tasks) { + builder.append("\n"); + builder.append(task.getName()); + task.cancel(true); + } + logger.log(Level.INFO, builder.toString()); + } + + //Futures.successfulAsList(tasks).get(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + platform.unload(); + } + + /** + * Get the version. + * + * @return the version of WorldEdit + */ + public static String getVersion() { + if (version != null) { + return version; + } + + Package p = WorldGuard.class.getPackage(); + + if (p == null) { + p = Package.getPackage("com.sk89q.worldguard"); + } + + if (p == null) { + version = "(unknown)"; + } else { + version = p.getImplementationVersion(); + + if (version == null) { + version = "(unknown)"; + } + } + + return version; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/Blacklist.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/Blacklist.java new file mode 100644 index 000000000..5d496a1a3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/Blacklist.java @@ -0,0 +1,267 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.blacklist.action.Action; +import com.sk89q.worldguard.blacklist.action.ActionType; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; +import com.sk89q.worldguard.blacklist.event.EventType; +import com.sk89q.worldguard.blacklist.target.TargetMatcher; +import com.sk89q.worldguard.blacklist.target.TargetMatcherParseException; +import com.sk89q.worldguard.blacklist.target.TargetMatcherParser; +import com.sk89q.worldguard.commands.CommandUtils; +import com.sk89q.worldguard.util.formatting.component.BlacklistNotify; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Blacklist { + + private static final Logger log = Logger.getLogger(Blacklist.class.getCanonicalName()); + + private MatcherIndex index = MatcherIndex.getEmptyInstance(); + private final BlacklistLoggerHandler blacklistLogger = new BlacklistLoggerHandler(); + private BlacklistEvent lastEvent; + private boolean useAsWhitelist; + private LoadingCache repeatingEventCache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(TrackedEvent::new)); + + public Blacklist(boolean useAsWhitelist) { + this.useAsWhitelist = useAsWhitelist; + } + + /** + * Returns whether the list is empty. + * + * @return whether the blacklist is empty + */ + public boolean isEmpty() { + return index.isEmpty(); + } + + /** + * Get the number of individual items that have blacklist entries. + * + * @return The number of items in the blacklist + */ + public int getItemCount() { + return index.size(); + } + + /** + * Returns whether the blacklist is used as a whitelist. + * + * @return whether the blacklist is be used as a whitelist + */ + public boolean isWhitelist() { + return useAsWhitelist; + } + + /** + * Get the log. + * + * @return The logger used in this blacklist + */ + public BlacklistLoggerHandler getLogger() { + return blacklistLogger; + } + + /** + * Method to handle the event. + * + * @param event The event to check + * @param forceRepeat Whether to force quickly repeating notifications + * @param silent Whether to force-deny notifications + * @return Whether the event is allowed + */ + public boolean check(BlacklistEvent event, boolean forceRepeat, boolean silent) { + List entries = index.getEntries(event.getTarget()); + + if (entries == null) { + return true; + } + + boolean ret = true; + + for (BlacklistEntry entry : entries) { + if (!entry.check(useAsWhitelist, event, forceRepeat, silent)) { + ret = false; + } + } + + return ret; + } + + /** + * Load the blacklist. + * + * @param file The file to load from + * @throws IOException if an error occurred reading from the file + */ + public void load(File file) throws IOException { + + MatcherIndex.Builder builder = new MatcherIndex.Builder(); + TargetMatcherParser targetMatcherParser = new TargetMatcherParser(); + try (FileReader input = new FileReader(file)) { + BufferedReader buff = new BufferedReader(input); + + String line; + List currentEntries = null; + while ((line = buff.readLine()) != null) { + line = line.trim(); + + // Blank line + if (line.isEmpty()) { + continue; + } else if (line.charAt(0) == ';' || line.charAt(0) == '#') { + continue; + } + + if (line.matches("^\\[.*\\]$")) { + String[] items = line.substring(1, line.length() - 1).split(","); + currentEntries = new ArrayList<>(); + + for (String item : items) { + try { + TargetMatcher matcher = targetMatcherParser.fromInput(item.trim()); + BlacklistEntry entry = new BlacklistEntry(this); + builder.add(matcher, entry); + currentEntries.add(entry); + } catch (TargetMatcherParseException e) { + log.log(Level.WARNING, "Could not parse a block/item heading: " + e.getMessage()); + } + } + } else if (currentEntries != null) { + String[] parts = line.split("="); + + if (parts.length == 1) { + log.log(Level.WARNING, "Found option with no value " + file.getName() + " for '" + line + "'"); + continue; + } + + boolean unknownOption = false; + + for (BlacklistEntry entry : currentEntries) { + if (parts[0].equalsIgnoreCase("ignore-groups")) { + entry.setIgnoreGroups(parts[1].split(",")); + + } else if (parts[0].equalsIgnoreCase("ignore-perms")) { + entry.setIgnorePermissions(parts[1].split(",")); + + } else if (parts[0].equalsIgnoreCase("message")) { + entry.setMessage(CommandUtils.replaceColorMacros(parts[1].trim())); + + } else if (parts[0].equalsIgnoreCase("comment")) { + entry.setComment(CommandUtils.replaceColorMacros(parts[1].trim())); + + } else { + boolean found = false; + + for (EventType type : EventType.values()) { + if (type.getRuleName().equalsIgnoreCase(parts[0])) { + entry.getActions(type.getEventClass()).addAll(parseActions(entry, parts[1])); + found = true; + break; + } + } + + if (!found) { + unknownOption = true; + } + } + } + + if (unknownOption) { + log.log(Level.WARNING, "Unknown option '" + parts[0] + "' in " + file.getName() + " for '" + line + "'"); + } + } else { + log.log(Level.WARNING, "Found option with no heading " + + file.getName() + " for '" + line + "'"); + } + } + + this.index = builder.build(); + } + } + + private List parseActions(BlacklistEntry entry, String raw) { + String[] split = raw.split(","); + List actions = new ArrayList<>(); + + for (String name : split) { + name = name.trim(); + + boolean found = false; + + for (ActionType type : ActionType.values()) { + if (type.getActionName().equalsIgnoreCase(name)) { + actions.add(type.parseInput(this, entry)); + found = true; + break; + } + } + + if (!found) { + log.log(Level.WARNING, "Unknown blacklist action: " + name); + } + } + + return actions; + } + + /** + * Get the last event. + * + * @return The last event + */ + public BlacklistEvent getLastEvent() { + return lastEvent; + } + + /** + * Notify administrators. + * + * @param event The event to notify about + * @param comment The comment to notify with + */ + public void notify(BlacklistEvent event, String comment) { + lastEvent = event; + + WorldGuard.getInstance().getPlatform().broadcastNotification(new BlacklistNotify(event, comment).create()); + } + + public LoadingCache getRepeatingEventCache() { + return repeatingEventCache; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistEntry.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistEntry.java new file mode 100644 index 000000000..6d52f1edf --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistEntry.java @@ -0,0 +1,211 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.action.Action; +import com.sk89q.worldguard.blacklist.action.ActionResult; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; +import com.google.common.cache.LoadingCache; + +import javax.annotation.Nullable; +import java.util.*; + +public class BlacklistEntry { + + private Blacklist blacklist; + private Set ignoreGroups; + private Set ignorePermissions; + private Map, List> actions = new HashMap<>(); + + private String message; + private String comment; + + /** + * Construct the object. + * + * @param blacklist The blacklist that contains this entry + */ + public BlacklistEntry(Blacklist blacklist) { + this.blacklist = blacklist; + } + + /** + * @return the ignoreGroups + */ + public String[] getIgnoreGroups() { + return ignoreGroups.toArray(new String[ignoreGroups.size()]); + } + + /** + * @return the ignoreGroups + */ + public String[] getIgnorePermissions() { + return ignorePermissions.toArray(new String[ignorePermissions.size()]); + } + + /** + * @param ignoreGroups the ignoreGroups to set + */ + public void setIgnoreGroups(String[] ignoreGroups) { + Set ignoreGroupsSet = new HashSet<>(); + for (String group : ignoreGroups) { + ignoreGroupsSet.add(group.toLowerCase()); + } + this.ignoreGroups = ignoreGroupsSet; + } + + /** + * @param ignorePermissions the ignorePermissions to set + */ + public void setIgnorePermissions(String[] ignorePermissions) { + Set ignorePermissionsSet = new HashSet<>(); + Collections.addAll(ignorePermissionsSet, ignorePermissions); + this.ignorePermissions = ignorePermissionsSet; + } + + /** + * @return the message + */ + public String getMessage() { + return message; + } + + /** + * @param message the message to set + */ + public void setMessage(String message) { + this.message = message; + } + + /** + * @return the comment + */ + public String getComment() { + return comment; + } + + /** + * @param comment the comment to set + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * Returns true if this player should be ignored. + * + * @param player The player to check + * @return whether this player should be ignored for blacklist blocking + */ + public boolean shouldIgnore(@Nullable LocalPlayer player) { + if (player == null) { + return false; // This is the case if the cause is a dispenser, for example + } + + if (ignoreGroups != null) { + for (String group : player.getGroups()) { + if (ignoreGroups.contains(group.toLowerCase())) { + return true; + } + } + } + + if (ignorePermissions != null) { + for (String perm : ignorePermissions) { + if (player.hasPermission(perm)) { + return true; + } + } + } + + return false; + } + + /** + * Get the associated actions with an event. + * + * @param eventCls The event's class + * @return The actions for the given event + */ + public List getActions(Class eventCls) { + return actions.computeIfAbsent(eventCls, k -> new ArrayList<>()); + } + + /** + * Method to handle the event. + * + * @param useAsWhitelist Whether this entry is being used in a whitelist + * @param event The event to check + * @param forceRepeat Whether to force repeating notifications even within the delay limit + * @param silent Whether to prevent notifications from happening + * @return Whether the action was allowed + */ + public boolean check(boolean useAsWhitelist, BlacklistEvent event, boolean forceRepeat, boolean silent) { + LocalPlayer player = event.getPlayer(); + + if (shouldIgnore(player)) { + return true; + } + + boolean repeating = false; + String eventCacheKey = event.getCauseName(); + LoadingCache repeatingEventCache = blacklist.getRepeatingEventCache(); + + // Check to see whether this event is being repeated + TrackedEvent tracked = repeatingEventCache.getUnchecked(eventCacheKey); + if (tracked.matches(event)) { + repeating = true; + } else { + tracked.setLastEvent(event); + tracked.resetTimer(); + } + + List actions = getActions(event.getClass()); + + boolean ret = !useAsWhitelist; + + // Nothing to do + if (actions == null) { + return ret; + } + + for (Action action : actions) { + ActionResult result = action.apply(event, silent, repeating, forceRepeat); + switch (result) { + case INHERIT: + continue; + case ALLOW: + ret = true; + break; + case DENY: + ret = false; + break; + case ALLOW_OVERRIDE: + return true; + case DENY_OVERRIDE: + return false; + } + } + + return ret; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistLoggerHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistLoggerHandler.java new file mode 100644 index 000000000..9cd3e9295 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/BlacklistLoggerHandler.java @@ -0,0 +1,83 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; +import com.sk89q.worldguard.blacklist.logger.LoggerHandler; + +import java.util.HashSet; +import java.util.Set; + +public class BlacklistLoggerHandler implements LoggerHandler { + + /** + * List of logger handlers. + */ + private Set handlers + = new HashSet<>(); + + /** + * Add a handler. + * + * @param handler The handler to add + */ + public void addHandler(LoggerHandler handler) { + handlers.add(handler); + } + + /** + * Remove a handler. + * + * @param handler The handler to remove + */ + public void removeHandler(LoggerHandler handler) { + handlers.remove(handler); + } + + /** + * Add a handler. + */ + public void clearHandlers() { + handlers.clear(); + } + + /** + * Log an event. + * + * @param event The event to log + */ + @Override + public void logEvent(BlacklistEvent event, String comment) { + for (LoggerHandler handler : handlers) { + handler.logEvent(event, comment); + } + } + + /** + * Close the connection. + */ + @Override + public void close() { + for (LoggerHandler handler : handlers) { + handler.close(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/MatcherIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/MatcherIndex.java new file mode 100644 index 000000000..76bcb915f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/MatcherIndex.java @@ -0,0 +1,80 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.sk89q.worldguard.blacklist.target.Target; +import com.sk89q.worldguard.blacklist.target.TargetMatcher; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import static com.google.common.base.Preconditions.checkNotNull; + +class MatcherIndex { + + private static final MatcherIndex EMPTY_INSTANCE = new MatcherIndex(HashBasedTable.create()); + private final Table entries; + + private MatcherIndex(Table entries) { + checkNotNull(entries); + this.entries = entries; + } + + public List getEntries(Target target) { + List found = new ArrayList<>(); + for (Entry entry : entries.row(target.getTypeId()).entrySet()) { + if (entry.getKey().test(target)) { + found.add(entry.getValue()); + } + } + return found; + } + + public int size() { + return entries.size(); + } + + public boolean isEmpty() { + return entries.isEmpty(); + } + + public static MatcherIndex getEmptyInstance() { + return EMPTY_INSTANCE; + } + + public static class Builder { + private final Table entries = HashBasedTable.create(); + + public Builder add(TargetMatcher matcher, BlacklistEntry entry) { + checkNotNull(matcher); + checkNotNull(entries); + entries.put(matcher.getMatchedTypeId(), matcher, entry); + return this; + } + + public MatcherIndex build() { + return new MatcherIndex(entries); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/TrackedEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/TrackedEvent.java new file mode 100644 index 000000000..3de0c76e5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/TrackedEvent.java @@ -0,0 +1,47 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import javax.annotation.Nullable; + +class TrackedEvent { + + @Nullable + private BlacklistEvent lastEvent; + private long time = 0; + + public boolean matches(BlacklistEvent other) { + if (lastEvent == null) { + return false; + } + long now = System.currentTimeMillis(); + return other.getEventType() == lastEvent.getEventType() && time > now - 3000; + } + + public void resetTimer() { + time = System.currentTimeMillis(); + } + + public void setLastEvent(@Nullable BlacklistEvent lastEvent) { + this.lastEvent = lastEvent; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/Action.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/Action.java new file mode 100644 index 000000000..ef9d69236 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/Action.java @@ -0,0 +1,28 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +public interface Action { + + ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionResult.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionResult.java new file mode 100644 index 000000000..f96b472ba --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionResult.java @@ -0,0 +1,30 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +public enum ActionResult { + + INHERIT, + DENY, + ALLOW, + DENY_OVERRIDE, + ALLOW_OVERRIDE + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionType.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionType.java new file mode 100644 index 000000000..c9a2379a1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/ActionType.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.blacklist.BlacklistEntry; + +public enum ActionType { + + ALLOW("allow") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return AllowAction.getInstance(); + } + }, + DENY("deny") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return DenyAction.getInstance(); + } + }, + BAN("ban") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return new BanAction(entry); + } + }, + KICK("kick") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return new KickAction(entry); + } + }, + LOG("log") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return new LogAction(blacklist, entry); + } + }, + NOTIFY("notify") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return new NotifyAction(blacklist, entry); + } + }, + TELL("tell") { + @Override + public Action parseInput(Blacklist blacklist, BlacklistEntry entry) { + return new TellAction(entry); + } + }; + + private final String actionName; + + ActionType(String actionName) { + this.actionName = actionName; + } + + public abstract Action parseInput(Blacklist blacklist, BlacklistEntry entry); + + public String getActionName() { + return actionName; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/AllowAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/AllowAction.java new file mode 100644 index 000000000..9186f3173 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/AllowAction.java @@ -0,0 +1,44 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +public final class AllowAction implements Action { + + private static final AllowAction INSTANCE = new AllowAction(); + + private AllowAction() { + } + + @Override + public ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat) { + if (silent) { + return ActionResult.ALLOW_OVERRIDE; + } + + return ActionResult.ALLOW; + } + + public static AllowAction getInstance() { + return INSTANCE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/BanAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/BanAction.java new file mode 100644 index 000000000..21d66ac68 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/BanAction.java @@ -0,0 +1,55 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.BlacklistEntry; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class BanAction implements Action { + + private final BlacklistEntry entry; + + public BanAction(BlacklistEntry entry) { + checkNotNull(entry); + this.entry = entry; + } + + @Override + public ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat) { + if (silent) { + return ActionResult.INHERIT; + } + + if (event.getPlayer() != null) { + String message = entry.getMessage(); + + if (message != null) { + event.getPlayer().ban("Banned: " + String.format(message, event.getTarget().getFriendlyName())); + } else { + event.getPlayer().ban("Banned: You can't " + event.getDescription() + " " + event.getTarget().getFriendlyName()); + } + } + + return ActionResult.INHERIT; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/DenyAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/DenyAction.java new file mode 100644 index 000000000..cb45a2501 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/DenyAction.java @@ -0,0 +1,44 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +public final class DenyAction implements Action { + + private static final DenyAction INSTANCE = new DenyAction(); + + private DenyAction() { + } + + @Override + public ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat) { + if (silent) { + return ActionResult.DENY_OVERRIDE; + } + + return ActionResult.DENY; + } + + public static DenyAction getInstance() { + return INSTANCE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/KickAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/KickAction.java new file mode 100644 index 000000000..baa3c010b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/KickAction.java @@ -0,0 +1,55 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.BlacklistEntry; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class KickAction implements Action { + + private final BlacklistEntry entry; + + public KickAction(BlacklistEntry entry) { + checkNotNull(entry); + this.entry = entry; + } + + @Override + public ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat) { + if (silent) { + return ActionResult.INHERIT; + } + + if (event.getPlayer() != null) { + String message = entry.getMessage(); + + if (message != null) { + event.getPlayer().kick(String.format(message, event.getTarget().getFriendlyName())); + } else { + event.getPlayer().kick("You can't " + event.getDescription() + " " + event.getTarget().getFriendlyName()); + } + } + + return ActionResult.INHERIT; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/LogAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/LogAction.java new file mode 100644 index 000000000..eb1399249 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/LogAction.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.blacklist.BlacklistEntry; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class LogAction extends RepeatGuardedAction { + + private final Blacklist blacklist; + private final BlacklistEntry entry; + + public LogAction(Blacklist blacklist, BlacklistEntry entry) { + checkNotNull(blacklist); + checkNotNull(entry); + this.blacklist = blacklist; + this.entry = entry; + } + + @Override + protected ActionResult applyNonRepeated(BlacklistEvent event, boolean silent) { + if (silent) { + return ActionResult.INHERIT; + } + + blacklist.getLogger().logEvent(event, entry.getComment()); + + return ActionResult.INHERIT; + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/NotifyAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/NotifyAction.java new file mode 100644 index 000000000..d5d17c540 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/NotifyAction.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.blacklist.BlacklistEntry; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class NotifyAction extends RepeatGuardedAction { + + private final Blacklist blacklist; + private final BlacklistEntry entry; + + public NotifyAction(Blacklist blacklist, BlacklistEntry entry) { + checkNotNull(blacklist); + checkNotNull(entry); + this.blacklist = blacklist; + this.entry = entry; + } + + @Override + protected ActionResult applyNonRepeated(BlacklistEvent event, boolean silent) { + if (silent) { + return ActionResult.INHERIT; + } + + blacklist.notify(event, entry.getComment()); + + return ActionResult.INHERIT; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/RepeatGuardedAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/RepeatGuardedAction.java new file mode 100644 index 000000000..829cbd05b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/RepeatGuardedAction.java @@ -0,0 +1,37 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +public abstract class RepeatGuardedAction implements Action { + + @Override + public final ActionResult apply(BlacklistEvent event, boolean silent, boolean repeating, boolean forceRepeat) { + if (!repeating || forceRepeat) { + return applyNonRepeated(event, silent); + } + + return ActionResult.INHERIT; + } + + protected abstract ActionResult applyNonRepeated(BlacklistEvent event, boolean silent); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/TellAction.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/TellAction.java new file mode 100644 index 000000000..29a503789 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/action/TellAction.java @@ -0,0 +1,56 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.action; + +import com.sk89q.worldguard.blacklist.BlacklistEntry; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class TellAction extends RepeatGuardedAction { + + private final BlacklistEntry entry; + + public TellAction(BlacklistEntry entry) { + checkNotNull(entry); + this.entry = entry; + } + + @Override + protected ActionResult applyNonRepeated(BlacklistEvent event, boolean silent) { + if (silent) { + return ActionResult.INHERIT; + } + + String message = entry.getMessage(); + + if (event.getPlayer() != null) { + if (message != null) { + message = message.replaceAll("(?!<\\\\)\\\\n", "\n").replaceAll("\\\\\\\\n", "\\n"); + event.getPlayer().print(String.format(message, event.getTarget().getFriendlyName())); + } else { + event.getPlayer().printError("You're not allowed to " + event.getDescription() + " " + event.getTarget().getFriendlyName() + "."); + } + } + + return ActionResult.INHERIT; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/AbstractBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/AbstractBlacklistEvent.java new file mode 100644 index 000000000..280c4aad4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/AbstractBlacklistEvent.java @@ -0,0 +1,77 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +abstract class AbstractBlacklistEvent implements BlacklistEvent { + + @Nullable + private final LocalPlayer player; + private final BlockVector3 position; + private final Target target; + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + AbstractBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + checkNotNull(position); + checkNotNull(target); + this.player = player; + this.position = position; + this.target = target; + } + + @Nullable + @Override + public LocalPlayer getPlayer() { + return player; + } + + @Override + public String getCauseName() { + return player != null ? player.getName() : position.toString(); + } + + @Override + public BlockVector3 getPosition() { + return position; + } + + @Override + public Target getTarget() { + return target; + } + + protected String getPlayerName() { + return player == null ? "(unknown)" : player.getName(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlacklistEvent.java new file mode 100644 index 000000000..03a74a0ce --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlacklistEvent.java @@ -0,0 +1,87 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public interface BlacklistEvent { + + /** + * Get the player. + * + * @return The player associated with this event + */ + @Nullable + LocalPlayer getPlayer(); + + /** + * Get the cause name, which is usually the player name. + * + * @return the cause name + */ + String getCauseName(); + + /** + * Get the position. + * + * @return The position of this event + */ + BlockVector3 getPosition(); + + /** + * Get the position that should be logged. + * + * @return The position that be logged. + */ + BlockVector3 getLoggedPosition(); + + /** + * Get the item type. + * + * @return The type associated with this event + */ + Target getTarget(); + + /** + * Get a short description such as "break" or "destroy with." + * + * @return The event description + */ + String getDescription(); + + /** + * Get a message for logger outputs. + * + * @return A logging message + */ + String getLoggerMessage(); + + /** + * Get the event type. + * + * @return the type + */ + EventType getEventType(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBlacklistEvent.java new file mode 100644 index 000000000..78f80299f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +abstract class BlockBlacklistEvent extends AbstractBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + BlockBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getLoggerMessage() { + return getPlayerName() + " tried to " + getDescription() + " " + getTarget().getFriendlyName(); + } + + @Override + public BlockVector3 getLoggedPosition() { + return getPosition(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBreakBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBreakBlacklistEvent.java new file mode 100644 index 000000000..2bbec7c4d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockBreakBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class BlockBreakBlacklistEvent extends BlockBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public BlockBreakBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "break"; + } + + @Override + public EventType getEventType() { + return EventType.BREAK; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockDispenseBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockDispenseBlacklistEvent.java new file mode 100644 index 000000000..0aa9f5711 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockDispenseBlacklistEvent.java @@ -0,0 +1,56 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class BlockDispenseBlacklistEvent extends BlockBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public BlockDispenseBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "dispense"; + } + + @Override + public String getLoggerMessage() { + return getPosition() + " tried to " + getDescription() + " " + getTarget().getFriendlyName(); + } + + @Override + public EventType getEventType() { + return EventType.DISPENSE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockInteractBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockInteractBlacklistEvent.java new file mode 100644 index 000000000..07d647ef3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockInteractBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class BlockInteractBlacklistEvent extends BlockBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public BlockInteractBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "interact with"; + } + + @Override + public EventType getEventType() { + return EventType.INTERACT; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockPlaceBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockPlaceBlacklistEvent.java new file mode 100644 index 000000000..34f36b675 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/BlockPlaceBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class BlockPlaceBlacklistEvent extends BlockBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public BlockPlaceBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "place"; + } + + @Override + public EventType getEventType() { + return EventType.PLACE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/EventType.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/EventType.java new file mode 100644 index 000000000..22202da59 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/EventType.java @@ -0,0 +1,50 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +public enum EventType { + + BREAK(BlockBreakBlacklistEvent.class, "on-break"), + PLACE(BlockPlaceBlacklistEvent.class, "on-place"), + INTERACT(BlockInteractBlacklistEvent.class, "on-interact"), + DISPENSE(BlockDispenseBlacklistEvent.class, "on-dispense"), + DESTROY_WITH(ItemDestroyWithBlacklistEvent.class, "on-destroy-with"), + ACQUIRE(ItemAcquireBlacklistEvent.class, "on-acquire"), + EQUIP(ItemEquipBlacklistEvent.class, "on-equip"), + DROP(ItemDropBlacklistEvent.class, "on-drop"), + USE(ItemUseBlacklistEvent.class, "on-use"); + + private final Class eventClass; + private final String ruleName; + + EventType(Class eventClass, String ruleName) { + this.eventClass = eventClass; + this.ruleName = ruleName; + } + + public Class getEventClass() { + return eventClass; + } + + public String getRuleName() { + return ruleName; + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemAcquireBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemAcquireBlacklistEvent.java new file mode 100644 index 000000000..eaa3b5346 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemAcquireBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class ItemAcquireBlacklistEvent extends ItemBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public ItemAcquireBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "acquire"; + } + + @Override + public EventType getEventType() { + return EventType.ACQUIRE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemBlacklistEvent.java new file mode 100644 index 000000000..4ce404f08 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +abstract class ItemBlacklistEvent extends AbstractBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + ItemBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getLoggerMessage() { + return getPlayerName() + " tried to " + getDescription() + " " + getTarget().getFriendlyName(); + } + + @Override + public BlockVector3 getLoggedPosition() { + return getPlayer() != null ? getPlayer().getLocation().toVector().toBlockPoint() : getPosition(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDestroyWithBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDestroyWithBlacklistEvent.java new file mode 100644 index 000000000..f9057af38 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDestroyWithBlacklistEvent.java @@ -0,0 +1,57 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class ItemDestroyWithBlacklistEvent extends ItemBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public ItemDestroyWithBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "destroy with"; + } + + @Override + public EventType getEventType() { + return EventType.DESTROY_WITH; + } + + @Override + public BlockVector3 getLoggedPosition() { + // Use the block position instead + return getPosition(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDropBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDropBlacklistEvent.java new file mode 100644 index 000000000..16708e9fb --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemDropBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class ItemDropBlacklistEvent extends ItemBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public ItemDropBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "drop"; + } + + @Override + public EventType getEventType() { + return EventType.DROP; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemEquipBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemEquipBlacklistEvent.java new file mode 100644 index 000000000..b8924807c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemEquipBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class ItemEquipBlacklistEvent extends ItemBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public ItemEquipBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "equip"; + } + + @Override + public EventType getEventType() { + return EventType.EQUIP; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemUseBlacklistEvent.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemUseBlacklistEvent.java new file mode 100644 index 000000000..7a1a2097e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/event/ItemUseBlacklistEvent.java @@ -0,0 +1,51 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.event; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.target.Target; + +import javax.annotation.Nullable; + +public final class ItemUseBlacklistEvent extends ItemBlacklistEvent { + + /** + * Construct the object. + * + * @param player The player associated with this event + * @param position The position the event occurred at + * @param target The target of the event + */ + public ItemUseBlacklistEvent(@Nullable LocalPlayer player, BlockVector3 position, Target target) { + super(player, position, target); + } + + @Override + public String getDescription() { + return "use"; + } + + @Override + public EventType getEventType() { + return EventType.USE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/ConsoleHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/ConsoleHandler.java new file mode 100644 index 000000000..8925a3ec6 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/ConsoleHandler.java @@ -0,0 +1,47 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.logger; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ConsoleHandler implements LoggerHandler { + + private String worldName; + private final Logger logger; + + public ConsoleHandler(String worldName, Logger logger) { + this.worldName = worldName; + this.logger = logger; + } + + @Override + public void logEvent(BlacklistEvent event, String comment) { + logger.log(Level.INFO, "[" + worldName + "] " + event.getLoggerMessage() + + (comment != null ? " (" + comment + ")" : "")); + } + + @Override + public void close() { + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/DatabaseHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/DatabaseHandler.java new file mode 100644 index 000000000..d43253ad4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/DatabaseHandler.java @@ -0,0 +1,126 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.logger; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; +import com.sk89q.worldguard.blacklist.event.EventType; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +public class DatabaseHandler implements LoggerHandler { + + private final String dsn; + private final String user; + private final String pass; + private final String table; + private final String worldName; + private Connection conn; + + private final Logger logger; + + /** + * Construct the object. + * + * @param dsn The DSN for the connection + * @param user The username to connect with + * @param pass The password to connect with + * @param table The table to log to + * @param worldName The name of the world to log + * @param logger The logger to log errors to + */ + public DatabaseHandler(String dsn, String user, String pass, String table, String worldName, Logger logger) { + this.dsn = dsn; + this.user = user; + this.pass = pass; + this.table = table; + this.worldName = worldName; + this.logger = logger; + } + + /** + * Gets the database connection. + * + * @return The database connection + * @throws SQLException when the connection cannot be created + */ + private Connection getConnection() throws SQLException { + if (conn == null || conn.isClosed()) { + conn = DriverManager.getConnection(dsn, user, pass); + } + return conn; + } + + /** + * Log an event to the database. + * + * @param eventType The event type to log + * @param player The player associated with the event + * @param pos The location of the event + * @param item The item used + * @param comment The comment associated with the event + */ + private void logEvent(EventType eventType, @Nullable LocalPlayer player, BlockVector3 pos, String item, String comment) { + try { + Connection conn = getConnection(); + PreparedStatement stmt = conn.prepareStatement( + "INSERT INTO " + table + + "(event, world, player, x, y, z, item, time, comment) VALUES " + + "(?, ?, ?, ?, ?, ?, ?, ?, ?)"); + stmt.setString(1, eventType.name()); + stmt.setString(2, worldName); + stmt.setString(3, player != null ? player.getName() : ""); + stmt.setInt(4, pos.getBlockX()); + stmt.setInt(5, pos.getBlockY()); + stmt.setInt(6, pos.getBlockZ()); + stmt.setString(7, item); + stmt.setInt(8, (int)(System.currentTimeMillis() / 1000)); + stmt.setString(9, comment); + stmt.executeUpdate(); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to log blacklist event to database: " + e.getMessage()); + } + } + + @Override + public void logEvent(BlacklistEvent event, String comment) { + logEvent(event.getEventType(), event.getPlayer(), event.getLoggedPosition(), event.getTarget().getTypeId(), comment); + } + + @Override + public void close() { + try { + if (conn != null && !conn.isClosed()) { + conn.close(); + } + } catch (SQLException ignore) { + + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java new file mode 100644 index 000000000..7ddc4bc6b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/FileHandler.java @@ -0,0 +1,245 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.logger; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; +import com.sk89q.worldguard.blacklist.target.Target; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FileHandler implements LoggerHandler { + + private static Pattern pattern = Pattern.compile("%."); + private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + private int cacheSize = 10; + private String pathPattern; + private String worldName; + private TreeMap writers = new TreeMap<>(); + + private final Logger logger; + + /** + * Construct the object. + * + * @param pathPattern The pattern for the log file path + * @param worldName The name of the world + * @param logger The logger used to log errors + */ + public FileHandler(String pathPattern, String worldName, Logger logger) { + this.pathPattern = pathPattern; + this.worldName = worldName; + this.logger = logger; + } + + /** + * Construct the object. + * + * @param pathPattern The pattern for logfile paths + * @param cacheSize The size of the file cache + * @param worldName The name of the associated world + * @param logger The logger to log errors with + */ + public FileHandler(String pathPattern, int cacheSize, String worldName, Logger logger) { + if (cacheSize < 1) { + throw new IllegalArgumentException("Cache size cannot be less than 1"); + } + this.pathPattern = pathPattern; + this.cacheSize = cacheSize; + this.worldName = worldName; + this.logger = logger; + } + + /** + * Build the path. + * + * @param playerName The name of the player + * @return The path for the logfile + */ + private String buildPath(String playerName) { + GregorianCalendar calendar = new GregorianCalendar(); + + Matcher m = pattern.matcher(pathPattern); + StringBuffer buffer = new StringBuffer(); + + // Pattern replacements + while (m.find()) { + String group = m.group(); + String rep = "?"; + + if (group.matches("%%")) { + rep = "%"; + } else if (group.matches("%u")) { + rep = playerName.toLowerCase().replaceAll("[^A-Za-z0-9_]", "_"); + if (rep.length() > 32) { // Actual max length is 16 + rep = rep.substring(0, 32); + } + + }else if (group.matches("%w")) { + rep = worldName.toLowerCase().replaceAll("[^A-Za-z0-9_]", "_"); + if (rep.length() > 32) { // Actual max length is 16 + rep = rep.substring(0, 32); + } + + // Date and time + } else if (group.matches("%Y")) { + rep = String.valueOf(calendar.get(Calendar.YEAR)); + } else if (group.matches("%m")) { + rep = String.format("%02d", calendar.get(Calendar.MONTH)); + } else if (group.matches("%d")) { + rep = String.format("%02d", calendar.get(Calendar.DAY_OF_MONTH)); + } else if (group.matches("%W")) { + rep = String.format("%02d", calendar.get(Calendar.WEEK_OF_YEAR)); + } else if (group.matches("%H")) { + rep = String.format("%02d", calendar.get(Calendar.HOUR_OF_DAY)); + } else if (group.matches("%h")) { + rep = String.format("%02d", calendar.get(Calendar.HOUR)); + } else if (group.matches("%i")) { + rep = String.format("%02d", calendar.get(Calendar.MINUTE)); + } else if (group.matches("%s")) { + rep = String.format("%02d", calendar.get(Calendar.SECOND)); + } + + m.appendReplacement(buffer, rep); + } + + m.appendTail(buffer); + + return buffer.toString(); + } + + /** + * Log a message. + * + * @param player The player to log + * @param message The message to log + * @param comment The comment associated with the logged event + */ + private void log(LocalPlayer player, String message, String comment) { + String path = buildPath(player.getName()); + try { + String date = dateFormat.format(new Date()); + String line = "[" + date + "] " + player.getName() + ": " + message + + (comment != null ? " (" + comment + ")" : "") + "\r\n"; + + LogFileWriter writer = writers.get(path); + + // Writer already exists! + if (writer != null) { + try { + BufferedWriter out = writer.getWriter(); + out.write(line); + out.flush(); + writer.updateLastUse(); + return; + } catch (IOException e) { + // Failed initial rewrite... let's re-open + } + } + + // Make parent directory + File file = new File(path); + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) { + parent.mkdirs(); + } + + FileWriter stream = new FileWriter(path, true); + BufferedWriter out = new BufferedWriter(stream); + out.write(line); + out.flush(); + writer = new LogFileWriter(path, out); + writers.put(path, writer); + + // Check to make sure our cache doesn't get too big! + if (writers.size() > cacheSize) { + Iterator> it = + writers.entrySet().iterator(); + + // Remove some entries + for (; it.hasNext(); ) { + Map.Entry entry = it.next(); + try { + entry.getValue().getWriter().close(); + } catch (IOException ignore) { + } + it.remove(); + + // Trimmed enough + if (writers.size() <= cacheSize) { + break; + } + } + } + + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to log blacklist event to '" + + path + "': " + e.getMessage()); + } + } + + /** + * Gets the coordinates in text form for the log. + * + * @param pos The position to get coordinates for + * @return The position's coordinates in human-readable form + */ + private String getCoordinates(BlockVector3 pos) { + return "@" + pos.getBlockX() + "," + pos.getBlockY() + "," + pos.getBlockZ(); + } + + private void logEvent(BlacklistEvent event, String text, Target target, BlockVector3 pos, String comment) { + log(event.getPlayer(), "Tried to " + text + " " + target.getFriendlyName() + " " + getCoordinates(pos), comment); + } + + @Override + public void logEvent(BlacklistEvent event, String comment) { + logEvent(event, event.getDescription(), event.getTarget(), event.getPosition(), comment); + } + + @Override + public void close() { + for (Map.Entry entry : writers.entrySet()) { + try { + entry.getValue().getWriter().close(); + } catch (IOException ignore) { + } + } + + writers.clear(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LogFileWriter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LogFileWriter.java new file mode 100644 index 000000000..69203a9c9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LogFileWriter.java @@ -0,0 +1,86 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.logger; + +import javax.annotation.Nullable; +import java.io.BufferedWriter; + +public class LogFileWriter implements Comparable { + + public String path; + private BufferedWriter writer; + private long lastUse; + + /** + * Construct the object. + * + * @param path The path to write to + * @param writer The writer for the file + */ + public LogFileWriter(String path, BufferedWriter writer) { + this.path = path; + this.writer = writer; + lastUse = System.currentTimeMillis(); + } + + /** + * File path. + * + * @return The path the logger is logging to + */ + public String getPath() { + return path; + } + + /** + * @return the writer being logged to + */ + public BufferedWriter getWriter() { + return writer; + } + + /** + * @return the lastUse + */ + public long getLastUse() { + return lastUse; + } + + /** + * Update last use time. + */ + public void updateLastUse() { + lastUse = System.currentTimeMillis(); + } + + @Override + public int compareTo(@Nullable LogFileWriter other) { + if (other == null) { + return 1; + } else if (lastUse > other.lastUse) { + return 1; + } else if (lastUse < other.lastUse) { + return -1; + } else { + return 0; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LoggerHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LoggerHandler.java new file mode 100644 index 000000000..f1fdb1998 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/blacklist/logger/LoggerHandler.java @@ -0,0 +1,42 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.blacklist.logger; + +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +/** + * Interface for loggers for the blacklist. + */ +public interface LoggerHandler { + + /** + * Log an event. + * + * @param event The event + * @param comment The comment to log with the event + */ + public void logEvent(BlacklistEvent event, String comment); + + /** + * Close the logger. + */ + public void close(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/ChestProtection.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/ChestProtection.java new file mode 100644 index 000000000..4b679642d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/ChestProtection.java @@ -0,0 +1,74 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.chest; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldguard.LocalPlayer; + +/** + * Interface for chest protection. + */ +public interface ChestProtection { + + /** + * Returns whether a block is protected. + * + * @param block The block to check + * @param player The player to check + * @return Whether the block is protected for player + */ + boolean isProtected(Location block, LocalPlayer player); + + /** + * Returns whether a location where a chest block is trying to be created + * is protected. + * + * @param block The block to check + * @param player The player to check + * @return Whether {@code player} can place a block at the specified block + */ + boolean isProtectedPlacement(Location block, LocalPlayer player); + + /** + * Returns whether an adjacent chest is protected. + * + * @param searchBlock The block to check + * @param player The player to check + * @return Whether {@code searchBlock} is protected from access by {@code player} + */ + boolean isAdjacentChestProtected(Location searchBlock, LocalPlayer player); + + /** + * Returns whether a blockType is a chest. + * + * @param blockType The blockType to check + * @return Whether a type is a 'chest' (protectable block) + */ + default boolean isChest(BlockType blockType) { + return blockType == BlockTypes.CHEST + || blockType == BlockTypes.DISPENSER + || blockType == BlockTypes.FURNACE + || blockType == BlockTypes.TRAPPED_CHEST + || blockType == BlockTypes.DROPPER; + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/SignChestProtection.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/SignChestProtection.java new file mode 100644 index 000000000..30260433f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/chest/SignChestProtection.java @@ -0,0 +1,117 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.chest; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldguard.LocalPlayer; + +/** + * Sign-based chest protection. + * + * @author sk89q + */ +public abstract class SignChestProtection implements ChestProtection { + + public abstract Boolean isProtectedSign(Location block, LocalPlayer player); + + public boolean isProtected(Location location, LocalPlayer player) { + com.sk89q.worldedit.world.block.BlockState blockState = location.getExtent().getBlock(location.toVector().toBlockPoint()); + if (isChest(blockState.getBlockType())) { + return isProtectedSignAround(location.setY(location.getY() - 1), player); + } else if (blockState.getBlockType() == BlockTypes.SIGN) { + return isProtectedSignAndChestBinary(location, player); + } else { + Boolean res = isProtectedSign(location.setY(location.getY() + 1), player); + if (res != null) return res; + return false; + } + } + + public boolean isProtectedPlacement(Location block, LocalPlayer player) { + return isProtectedSignAround(block, player); + } + + private boolean isProtectedSignAround(Location searchBlock, LocalPlayer player) { + Location side; + Boolean res; + + side = searchBlock; + res = isProtectedSign(side, player); + if (res != null && res) return res; + + side = searchBlock.setX(searchBlock.getX() - 1); + res = isProtectedSignAndChest(side, player); + if (res != null && res) return res; + + side = searchBlock.setX(searchBlock.getX() + 1); + res = isProtectedSignAndChest(side, player); + if (res != null && res) return res; + + side = searchBlock.setZ(searchBlock.getZ() - 1); + res = isProtectedSignAndChest(side, player); + if (res != null && res) return res; + + side = searchBlock.setZ(searchBlock.getZ() + 1); + res = isProtectedSignAndChest(side, player); + if (res != null && res) return res; + + return false; + } + + private Boolean isProtectedSignAndChest(Location block, LocalPlayer player) { + if (!isChest(block.getExtent().getBlock(block.setY(block.getY() + 1).toVector().toBlockPoint()).getBlockType())) { + return null; + } + return isProtectedSign(block, player); + } + + private boolean isProtectedSignAndChestBinary(Location block, LocalPlayer player) { + Boolean res = isProtectedSignAndChest(block, player); + return !(res == null || !res); + } + + public boolean isAdjacentChestProtected(Location searchBlock, LocalPlayer player) { + Location side; + boolean res; + + side = searchBlock; + res = isProtected(side, player); + if (res) return res; + + side = searchBlock.setX(searchBlock.getX() - 1); + res = isProtected(side, player); + if (res) return res; + + side = searchBlock.setX(searchBlock.getX() + 1); + res = isProtected(side, player); + if (res) return res; + + side = searchBlock.setZ(searchBlock.getZ() - 1); + res = isProtected(side, player); + if (res) return res; + + side = searchBlock.setZ(searchBlock.getZ() + 1); + res = isProtected(side, player); + if (res) return res; + + return false; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/CommandUtils.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/CommandUtils.java new file mode 100644 index 000000000..b8343fa8b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/CommandUtils.java @@ -0,0 +1,136 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.serializer.legacy.LegacyComponentSerializer; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Command-related utility methods. + */ +public final class CommandUtils { + + private CommandUtils() { + } + + /** + * Replace color macros in a string. + * + * @param str the string + * @return the new string + */ + public static String replaceColorMacros(String str) { + // TODO: Make this more efficient + + str = str.replace("`r", "&c"); + str = str.replace("`R", "&4"); + + str = str.replace("`y", "&e"); + str = str.replace("`Y", "&6"); + + str = str.replace("`g", "&a"); + str = str.replace("`G", "&2"); + + str = str.replace("`c", "&b"); + str = str.replace("`C", "&3"); + + str = str.replace("`b", "&9"); + str = str.replace("`B", "&1"); + + str = str.replace("`p", "&d"); + str = str.replace("`P", "&5"); + + str = str.replace("`0", "&0"); + str = str.replace("`1", "&8"); + str = str.replace("`2", "&7"); + str = str.replace("`w", "&F"); + + str = str.replace("`k", "&k"); + + str = str.replace("`l", "&l"); + str = str.replace("`m", "&m"); + str = str.replace("`n", "&n"); + str = str.replace("`o", "&o"); + + str = str.replace("`x", "&r"); + + // MC classic + // FIXME: workaround for https://github.com/KyoriPowered/text/issues/50 + // remove when fixed upstream and updated in WorldEdit + str = Arrays.stream(str.split("\n")).map(line -> { + TextComponent comp = LegacyComponentSerializer.INSTANCE.deserialize(line, '&'); + return LegacyComponentSerializer.INSTANCE.serialize(comp); + }).collect(Collectors.joining("\n")); + + return str; + } + + + /** + * Get the name of the given owner object. + * + * @param owner the owner object + * @return a name + */ + public static String getOwnerName(@Nullable Object owner) { + if (owner == null) { + return "?"; + } else if (owner instanceof Actor) { + return ((Actor) owner).getName(); + } else { + return "?"; + } + } + + /** + * Return a function that accepts a string to send a message to the + * given sender. + * + * @param sender the sender + * @return a function + */ + public static Function messageFunction(final Actor sender) { + return s -> { + sender.printRaw(s); + return null; + }; + } + + /** + * Return a function that accepts a TextComponent to send a message to the + * given sender. + * + * @param sender the sender + * @return a function + */ + public static Function messageComponentFunction(final Actor sender) { + return s -> { + sender.print(s); + return null; + }; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/DebuggingCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/DebuggingCommands.java new file mode 100644 index 000000000..8703d0f8a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/DebuggingCommands.java @@ -0,0 +1,71 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; + +public class DebuggingCommands { + + private final WorldGuard worldGuard; + + /** + * Create a new instance. + * + * @param worldGuard The worldGuard instance + */ + public DebuggingCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"testbreak"}, usage = "[player]", desc = "Simulate a block break", min = 1, max = 1, flags = "ts") + @CommandPermissions("worldguard.debug.event") + public void fireBreakEvent(CommandContext args, final Actor sender) throws CommandException { + LocalPlayer target = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + worldGuard.getPlatform().getDebugHandler().testBreak(sender, target, args.hasFlag('t'), args.hasFlag('s')); + } + + + @Command(aliases = {"testplace"}, usage = "[player]", desc = "Simulate a block place", min = 1, max = 1, flags = "ts") + @CommandPermissions("worldguard.debug.event") + public void firePlaceEvent(CommandContext args, final Actor sender) throws CommandException { + LocalPlayer target = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + worldGuard.getPlatform().getDebugHandler().testPlace(sender, target, args.hasFlag('t'), args.hasFlag('s')); + } + + @Command(aliases = {"testinteract"}, usage = "[player]", desc = "Simulate a block interact", min = 1, max = 1, flags = "ts") + @CommandPermissions("worldguard.debug.event") + public void fireInteractEvent(CommandContext args, final Actor sender) throws CommandException { + LocalPlayer target = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + worldGuard.getPlatform().getDebugHandler().testInteract(sender, target, args.hasFlag('t'), args.hasFlag('s')); + } + + @Command(aliases = {"testdamage"}, usage = "[player]", desc = "Simulate an entity damage", min = 1, max = 1, flags = "ts") + @CommandPermissions("worldguard.debug.event") + public void fireDamageEvent(CommandContext args, final Actor sender) throws CommandException { + LocalPlayer target = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + worldGuard.getPlatform().getDebugHandler().testDamage(sender, target, args.hasFlag('t'), args.hasFlag('s')); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/GeneralCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/GeneralCommands.java new file mode 100644 index 000000000..1b8fffcef --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/GeneralCommands.java @@ -0,0 +1,241 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.google.common.collect.Lists; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.handler.GodMode; + +public class GeneralCommands { + private final WorldGuard worldGuard; + + public GeneralCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"god"}, usage = "[player]", + desc = "Enable godmode on a player", flags = "s", max = 1) + public void god(CommandContext args, Actor sender) throws CommandException, AuthorizationException { + Iterable targets = null; + boolean included = false; + + // Detect arguments based on the number of arguments provided + if (args.argsLength() == 0) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(worldGuard.checkPlayer(sender)); + + // Check permissions! + sender.checkPermission("worldguard.god"); + } else { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(sender, args.getString(0)); + + // Check permissions! + sender.checkPermission("worldguard.god.other"); + } + + for (LocalPlayer player : targets) { + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + + if (GodMode.set(player, session, true)) { + player.setFireTicks(0); + + // Tell the user + if (player.equals(sender)) { + player.print("God mode enabled! Use /ungod to disable."); + + // Keep track of this + included = true; + } else { + player.print("God enabled by " + sender.getDisplayName() + "."); + + } + } + } + + // The player didn't receive any items, then we need to send the + // user a message so s/he know that something is indeed working + if (!included && args.hasFlag('s')) { + sender.print("Players now have god mode."); + } + } + + @Command(aliases = {"ungod"}, usage = "[player]", + desc = "Disable godmode on a player", flags = "s", max = 1) + public void ungod(CommandContext args, Actor sender) throws CommandException, AuthorizationException { + Iterable targets; + boolean included = false; + + // Detect arguments based on the number of arguments provided + if (args.argsLength() == 0) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(worldGuard.checkPlayer(sender)); + + // Check permissions! + sender.checkPermission("worldguard.god"); + } else { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(sender, args.getString(0)); + + // Check permissions! + sender.checkPermission("worldguard.god.other"); + } + + for (LocalPlayer player : targets) { + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + + if (GodMode.set(player, session, false)) { + // Tell the user + if (player.equals(sender)) { + player.print("God mode disabled!"); + + // Keep track of this + included = true; + } else { + player.print("God disabled by " + sender.getDisplayName() + "."); + + } + } + } + + // The player didn't receive any items, then we need to send the + // user a message so s/he know that something is indeed working + if (!included && args.hasFlag('s')) { + sender.print("Players no longer have god mode."); + } + } + + @Command(aliases = {"heal"}, usage = "[player]", desc = "Heal a player", flags = "s", max = 1) + public void heal(CommandContext args, Actor sender) throws CommandException, AuthorizationException { + + Iterable targets = null; + boolean included = false; + + // Detect arguments based on the number of arguments provided + if (args.argsLength() == 0) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(worldGuard.checkPlayer(sender)); + + // Check permissions! + sender.checkPermission("worldguard.heal"); + } else if (args.argsLength() == 1) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(sender, args.getString(0)); + + // Check permissions! + sender.checkPermission("worldguard.heal.other"); + } + + for (LocalPlayer player : targets) { + player.setHealth(player.getMaxHealth()); + player.setFoodLevel(20); + player.setSaturation(20); + player.setExhaustion(0); + + // Tell the user + if (player.equals(sender)) { + player.print("Healed!"); + + // Keep track of this + included = true; + } else { + player.print("Healed by " + sender.getDisplayName() + "."); + + } + } + + // The player didn't receive any items, then we need to send the + // user a message so s/he know that something is indeed working + if (!included && args.hasFlag('s')) { + sender.print("Players healed."); + } + } + + @Command(aliases = {"slay"}, usage = "[player]", desc = "Slay a player", flags = "s", max = 1) + public void slay(CommandContext args, Actor sender) throws CommandException, AuthorizationException { + + Iterable targets = Lists.newArrayList(); + boolean included = false; + + // Detect arguments based on the number of arguments provided + if (args.argsLength() == 0) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(worldGuard.checkPlayer(sender)); + + // Check permissions! + sender.checkPermission("worldguard.slay"); + } else if (args.argsLength() == 1) { + targets = worldGuard.getPlatform().getMatcher().matchPlayers(sender, args.getString(0)); + + // Check permissions! + sender.checkPermission("worldguard.slay.other"); + } + + for (LocalPlayer player : targets) { + player.setHealth(0); + + // Tell the user + if (player.equals(sender)) { + player.print("Slain!"); + + // Keep track of this + included = true; + } else { + player.print("Slain by " + sender.getDisplayName() + "."); + + } + } + + // The player didn't receive any items, then we need to send the + // user a message so s/he know that something is indeed working + if (!included && args.hasFlag('s')) { + sender.print("Players slain."); + } + } + + @Command(aliases = {"locate"}, usage = "[player]", desc = "Locate a player", max = 1) + @CommandPermissions({"worldguard.locate"}) + public void locate(CommandContext args, Actor sender) throws CommandException { + LocalPlayer player = worldGuard.checkPlayer(sender); + + if (args.argsLength() == 0) { + player.setCompassTarget(new Location(player.getWorld(), player.getWorld().getSpawnPosition().toVector3())); + + sender.print("Compass reset to spawn."); + } else { + LocalPlayer target = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + player.setCompassTarget(target.getLocation()); + + sender.print("Compass repointed."); + } + } + + @Command(aliases = {"stack", ";"}, usage = "", desc = "Stack items", max = 0) + @CommandPermissions({"worldguard.stack"}) + public void stack(CommandContext args, Actor sender) throws CommandException { + LocalPlayer player = worldGuard.checkPlayer(sender); + + WorldGuard.getInstance().getPlatform().stackPlayerInventory(player); + + player.print("Items compacted into stacks!"); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ProtectionCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ProtectionCommands.java new file mode 100644 index 000000000..ab1dcf4d3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ProtectionCommands.java @@ -0,0 +1,45 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.NestedCommand; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.commands.region.MemberCommands; +import com.sk89q.worldguard.commands.region.RegionCommands; + +public class ProtectionCommands { + @SuppressWarnings("unused") + private final WorldGuard worldGuard; + + public ProtectionCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"region", "regions", "rg"}, desc = "Region management commands") + @NestedCommand({RegionCommands.class, MemberCommands.class}) + public void region(CommandContext args, Actor sender) {} + + @Command(aliases = {"worldguard", "wg"}, desc = "WorldGuard commands") + @NestedCommand({WorldGuardCommands.class}) + public void worldGuard(CommandContext args, Actor sender) {} +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ToggleCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ToggleCommands.java new file mode 100644 index 000000000..91fbe15ef --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/ToggleCommands.java @@ -0,0 +1,182 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.util.formatting.component.CodeFormat; +import com.sk89q.worldedit.util.formatting.component.ErrorFormat; +import com.sk89q.worldedit.util.formatting.component.LabelFormat; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.util.Entities; + +public class ToggleCommands { + private final WorldGuard worldGuard; + + public ToggleCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"stopfire"}, usage = "[]", + desc = "Disables all fire spread temporarily", max = 1) + @CommandPermissions({"worldguard.fire-toggle.stop"}) + public void stopFire(CommandContext args, Actor sender) throws CommandException { + + World world; + + if (args.argsLength() == 0) { + world = worldGuard.checkPlayer(sender).getWorld(); + } else { + world = worldGuard.getPlatform().getMatcher().matchWorld(sender, args.getString(0)); + } + + WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + + if (!wcfg.fireSpreadDisableToggle) { + worldGuard.getPlatform().broadcastNotification( + LabelFormat.wrap("Fire spread has been globally disabled for '" + world.getName() + "' by " + + sender.getDisplayName() + ".")); + } else { + sender.print("Fire spread was already globally disabled."); + } + + wcfg.fireSpreadDisableToggle = true; + } + + @Command(aliases = {"allowfire"}, usage = "[]", + desc = "Allows all fire spread temporarily", max = 1) + @CommandPermissions({"worldguard.fire-toggle.stop"}) + public void allowFire(CommandContext args, Actor sender) throws CommandException { + + World world; + + if (args.argsLength() == 0) { + world = worldGuard.checkPlayer(sender).getWorld(); + } else { + world = worldGuard.getPlatform().getMatcher().matchWorld(sender, args.getString(0)); + } + + WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + + if (wcfg.fireSpreadDisableToggle) { + worldGuard.getPlatform().broadcastNotification(LabelFormat.wrap("Fire spread has been globally for '" + world.getName() + "' re-enabled by " + + sender.getDisplayName() + ".")); + } else { + sender.print("Fire spread was already globally enabled."); + } + + wcfg.fireSpreadDisableToggle = false; + } + + @Command(aliases = {"halt-activity", "stoplag", "haltactivity"}, usage = "[confirm]", + desc = "Attempts to cease as much activity in order to stop lag", flags = "cis", max = 1) + @CommandPermissions({"worldguard.halt-activity"}) + public void stopLag(CommandContext args, Actor sender) throws CommandException { + + ConfigurationManager configManager = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + + if (args.hasFlag('i')) { + if (configManager.activityHaltToggle) { + sender.print("ALL intensive server activity is not allowed."); + } else { + sender.print("ALL intensive server activity is allowed."); + } + } else { + boolean activityHaltToggle = !args.hasFlag('c'); + + if (activityHaltToggle && (args.argsLength() == 0 || !args.getString(0).equalsIgnoreCase("confirm"))) { + String confirmCommand = "/" + args.getCommand() + " confirm"; + + TextComponent message = TextComponent.builder("") + .append(ErrorFormat.wrap("This command will ")) + .append(ErrorFormat.wrap("PERMANENTLY") + .decoration(TextDecoration.BOLD, TextDecoration.State.TRUE)) + .append(ErrorFormat.wrap(" erase ALL animals in ALL loaded chunks in ALL loaded worlds. ")) + .append(TextComponent.newline()) + .append(TextComponent.of("[Click]", TextColor.GREEN) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, confirmCommand)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to confirm /" + args.getCommand())))) + .append(ErrorFormat.wrap(" or type ")) + .append(CodeFormat.wrap(confirmCommand) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, confirmCommand))) + .append(ErrorFormat.wrap(" to confirm.")) + .build(); + + sender.print(message); + return; + } + + configManager.activityHaltToggle = activityHaltToggle; + + if (activityHaltToggle) { + if (!(sender instanceof LocalPlayer)) { + sender.print("ALL intensive server activity halted."); + } + + if (!args.hasFlag('s')) { + worldGuard.getPlatform().broadcastNotification(LabelFormat.wrap("ALL intensive server activity halted by " + sender.getDisplayName() + ".")); + } else { + sender.print("(Silent) ALL intensive server activity halted by " + sender.getDisplayName() + "."); + } + + for (World world : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + int removed = 0; + + for (Entity entity : world.getEntities()) { + if (Entities.isIntensiveEntity(entity)) { + entity.remove(); + removed++; + } + } + + if (removed > 10) { + sender.printRaw("" + removed + " entities (>10) auto-removed from " + + world.getName()); + } + } + } else { + if (!args.hasFlag('s')) { + worldGuard.getPlatform().broadcastNotification(LabelFormat.wrap("ALL intensive server activity is now allowed.")); + + if (!(sender instanceof LocalPlayer)) { + sender.print("ALL intensive server activity is now allowed."); + } + } else { + sender.print("(Silent) ALL intensive server activity is now allowed."); + } + } + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/WorldGuardCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/WorldGuardCommands.java new file mode 100644 index 000000000..3734ae9a1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/WorldGuardCommands.java @@ -0,0 +1,309 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands; + +import com.google.common.io.Files; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissions; +import com.sk89q.minecraft.util.commands.NestedCommand; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.util.formatting.component.MessageBox; +import com.sk89q.worldedit.util.formatting.component.TextComponentProducer; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.paste.ActorCallbackPaste; +import com.sk89q.worldedit.util.report.ReportList; +import com.sk89q.worldedit.util.report.SystemInfoReport; +import com.sk89q.worldedit.util.task.FutureForwardingTask; +import com.sk89q.worldedit.util.task.Task; +import com.sk89q.worldedit.util.task.TaskStateComparator; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.util.logging.LoggerToChatHandler; +import com.sk89q.worldguard.util.profiler.SamplerBuilder; +import com.sk89q.worldguard.util.profiler.SamplerBuilder.Sampler; +import com.sk89q.worldguard.util.profiler.ThreadIdFilter; +import com.sk89q.worldguard.util.profiler.ThreadNameFilter; +import com.sk89q.worldguard.util.report.ApplicableRegionsReport; +import com.sk89q.worldguard.util.report.ConfigReport; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ThreadInfo; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +public class WorldGuardCommands { + + private final WorldGuard worldGuard; + @Nullable + private Sampler activeSampler; + + public WorldGuardCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"version"}, desc = "Get the WorldGuard version", max = 0) + public void version(CommandContext args, Actor sender) throws CommandException { + sender.print("WorldGuard " + WorldGuard.getVersion()); + sender.print("http://www.enginehub.org"); + + sender.printDebug("----------- Platforms -----------"); + sender.printDebug(String.format("* %s (%s)", worldGuard.getPlatform().getPlatformName(), worldGuard.getPlatform().getPlatformVersion())); + } + + @Command(aliases = {"reload"}, desc = "Reload WorldGuard configuration", max = 0) + @CommandPermissions({"worldguard.reload"}) + public void reload(CommandContext args, Actor sender) throws CommandException { + // TODO: This is subject to a race condition, but at least other commands are not being processed concurrently + List> tasks = WorldGuard.getInstance().getSupervisor().getTasks(); + if (!tasks.isEmpty()) { + throw new CommandException("There are currently pending tasks. Use /wg running to monitor these tasks first."); + } + + LoggerToChatHandler handler = null; + Logger minecraftLogger = null; + + if (sender instanceof LocalPlayer) { + handler = new LoggerToChatHandler(sender); + handler.setLevel(Level.ALL); + minecraftLogger = Logger.getLogger("com.sk89q.worldguard"); + minecraftLogger.addHandler(handler); + } + + try { + ConfigurationManager config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + config.unload(); + config.load(); + for (World world : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + config.get(world); + } + WorldGuard.getInstance().getPlatform().getRegionContainer().reload(); + // WGBukkit.cleanCache(); + sender.print("WorldGuard configuration reloaded."); + } catch (Throwable t) { + sender.printError("Error while reloading: " + t.getMessage()); + } finally { + if (minecraftLogger != null) { + minecraftLogger.removeHandler(handler); + } + } + } + + @Command(aliases = {"report"}, desc = "Writes a report on WorldGuard", flags = "p", max = 0) + @CommandPermissions({"worldguard.report"}) + public void report(CommandContext args, final Actor sender) throws CommandException, AuthorizationException { + ReportList report = new ReportList("Report"); + worldGuard.getPlatform().addPlatformReports(report); + report.add(new SystemInfoReport()); + report.add(new ConfigReport()); + if (sender instanceof LocalPlayer) { + report.add(new ApplicableRegionsReport((LocalPlayer) sender)); + } + String result = report.toString(); + + try { + File dest = new File(worldGuard.getPlatform().getConfigDir().toFile(), "report.txt"); + Files.write(result, dest, StandardCharsets.UTF_8); + sender.print("WorldGuard report written to " + dest.getAbsolutePath()); + } catch (IOException e) { + throw new CommandException("Failed to write report: " + e.getMessage()); + } + + if (args.hasFlag('p')) { + sender.checkPermission("worldguard.report.pastebin"); + ActorCallbackPaste.pastebin(worldGuard.getSupervisor(), sender, result, "WorldGuard report: %s.report"); + } + } + + @Command(aliases = {"profile"}, usage = "[-p] [-i ] [-t ] []", + desc = "Profile the CPU usage of the server", min = 0, max = 1, + flags = "t:i:p") + @CommandPermissions("worldguard.profile") + public void profile(final CommandContext args, final Actor sender) throws CommandException, AuthorizationException { + Predicate threadFilter; + String threadName = args.getFlag('t'); + final boolean pastebin; + + if (args.hasFlag('p')) { + sender.checkPermission("worldguard.report.pastebin"); + pastebin = true; + } else { + pastebin = false; + } + + if (threadName == null) { + threadFilter = new ThreadIdFilter(Thread.currentThread().getId()); + } else if (threadName.equals("*")) { + threadFilter = thread -> true; + } else { + threadFilter = new ThreadNameFilter(threadName); + } + + int minutes; + if (args.argsLength() == 0) { + minutes = 5; + } else { + minutes = args.getInteger(0); + if (minutes < 1) { + throw new CommandException("You must run the profile for at least 1 minute."); + } else if (minutes > 10) { + throw new CommandException("You can profile for, at maximum, 10 minutes."); + } + } + + int interval = 20; + if (args.hasFlag('i')) { + interval = args.getFlagInteger('i'); + if (interval < 1 || interval > 100) { + throw new CommandException("Interval must be between 1 and 100 (in milliseconds)"); + } + if (interval < 10) { + sender.printDebug("Note: A low interval may cause additional slowdown during profiling."); + } + } + Sampler sampler; + + synchronized (this) { + if (activeSampler != null) { + throw new CommandException("A profile is currently in progress! Please use /wg stopprofile to cancel the current profile."); + } + + SamplerBuilder builder = new SamplerBuilder(); + builder.setThreadFilter(threadFilter); + builder.setRunTime(minutes, TimeUnit.MINUTES); + builder.setInterval(interval); + sampler = activeSampler = builder.start(); + } + + sender.print(TextComponent.of("Starting CPU profiling. Results will be available in " + minutes + " minutes.", TextColor.LIGHT_PURPLE) + .append(TextComponent.newline()) + .append(TextComponent.of("Use ", TextColor.GRAY)) + .append(TextComponent.of("/wg stopprofile", TextColor.AQUA) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, "/wg stopprofile"))) + .append(TextComponent.of(" at any time to cancel CPU profiling.", TextColor.GRAY))); + + worldGuard.getSupervisor().monitor(FutureForwardingTask.create( + sampler.getFuture(), "CPU profiling for " + minutes + " minutes", sender)); + + sampler.getFuture().addListener(() -> { + synchronized (WorldGuardCommands.this) { + activeSampler = null; + } + }, MoreExecutors.directExecutor()); + + Futures.addCallback(sampler.getFuture(), new FutureCallback() { + @Override + public void onSuccess(Sampler result) { + String output = result.toString(); + + try { + File dest = new File(worldGuard.getPlatform().getConfigDir().toFile(), "profile.txt"); + Files.write(output, dest, StandardCharsets.UTF_8); + sender.print("CPU profiling data written to " + dest.getAbsolutePath()); + } catch (IOException e) { + sender.printError("Failed to write CPU profiling data: " + e.getMessage()); + } + + if (pastebin) { + ActorCallbackPaste.pastebin(worldGuard.getSupervisor(), sender, output, "Profile result: %s.profile"); + } + } + + @Override + public void onFailure(Throwable throwable) { + } + }); + } + + @Command(aliases = {"stopprofile"}, usage = "",desc = "Stop a running profile", min = 0, max = 0) + @CommandPermissions("worldguard.profile") + public void stopProfile(CommandContext args, final Actor sender) throws CommandException { + synchronized (this) { + if (activeSampler == null) { + throw new CommandException("No CPU profile is currently running."); + } + + activeSampler.cancel(); + activeSampler = null; + } + + sender.print("The running CPU profile has been cancelled."); + } + + @Command(aliases = {"flushstates", "clearstates"}, + usage = "[player]", desc = "Flush the state manager", max = 1) + @CommandPermissions("worldguard.flushstates") + public void flushStates(CommandContext args, Actor sender) throws CommandException { + if (args.argsLength() == 0) { + WorldGuard.getInstance().getPlatform().getSessionManager().resetAllStates(); + sender.print("Cleared all states."); + } else { + LocalPlayer player = worldGuard.getPlatform().getMatcher().matchSinglePlayer(sender, args.getString(0)); + if (player != null) { + WorldGuard.getInstance().getPlatform().getSessionManager().resetState(player); + sender.print("Cleared states for player \"" + player.getName() + "\"."); + } + } + } + + @Command(aliases = {"running", "queue"}, desc = "List running tasks", max = 0) + @CommandPermissions("worldguard.running") + public void listRunningTasks(CommandContext args, Actor sender) throws CommandException { + List> tasks = WorldGuard.getInstance().getSupervisor().getTasks(); + + if (tasks.isEmpty()) { + sender.print("There are currently no running tasks."); + } else { + tasks.sort(new TaskStateComparator()); + MessageBox builder = new MessageBox("Running Tasks", new TextComponentProducer()); + builder.append(TextComponent.of("Note: Some 'running' tasks may be waiting to be start.", TextColor.GRAY)); + for (Task task : tasks) { + builder.append(TextComponent.newline()); + builder.append(TextComponent.of("(" + task.getState().name() + ") ", TextColor.BLUE)); + builder.append(TextComponent.of(CommandUtils.getOwnerName(task.getOwner()) + ": ", TextColor.YELLOW)); + builder.append(TextComponent.of(task.getName(), TextColor.WHITE)); + } + sender.print(builder.create()); + } + } + + @Command(aliases = {"debug"}, desc = "Debugging commands") + @NestedCommand({DebuggingCommands.class}) + public void debug(CommandContext args, Actor sender) {} + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/FlagHelperBox.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/FlagHelperBox.java new file mode 100644 index 000000000..5b304bc34 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/FlagHelperBox.java @@ -0,0 +1,500 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.region; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.Registry; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.component.PaginationBox; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; +import com.sk89q.worldedit.util.formatting.text.serializer.legacy.LegacyComponentSerializer; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.flags.BooleanFlag; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.LocationFlag; +import com.sk89q.worldguard.protection.flags.NumberFlag; +import com.sk89q.worldguard.protection.flags.RegistryFlag; +import com.sk89q.worldguard.protection.flags.SetFlag; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StringFlag; +import com.sk89q.worldguard.protection.flags.registry.UnknownFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +class FlagHelperBox extends PaginationBox { + + private static final List> FLAGS = WorldGuard.getInstance().getFlagRegistry().getAll().stream() + .sorted((f1, f2) -> { + if (f1 == f2) return 0; + int idx1 = Flags.INBUILT_FLAGS.indexOf(f1.getName()); + int idx2 = Flags.INBUILT_FLAGS.indexOf(f2.getName()); + if (idx1 < 0 && idx2 >= 0) return 1; + if (idx2 < 0 && idx1 >= 0) return -1; + if (idx1 < 0) return f1.getName().compareTo(f2.getName()); + return idx1 < idx2 ? -1 : 1; + }) + .collect(Collectors.toList()); + private static final int SIZE = FLAGS.size() == Flags.INBUILT_FLAGS.size() ? FLAGS.size() : FLAGS.size() + 1; + private static final int PAD_PX_SIZE = 180; + static final Set> DANGER_ZONE = ImmutableSet.of(Flags.BUILD, Flags.PASSTHROUGH, Flags.BLOCK_PLACE, Flags.BLOCK_BREAK); + + private final World world; + private final ProtectedRegion region; + private final RegionPermissionModel perms; + private boolean monoSpace; + + FlagHelperBox(World world, ProtectedRegion region, RegionPermissionModel perms) { + super("Flags for " + region.getId(), "/rg flags -w \"" + world.getName() + "\" -p %page% " + region.getId()); + this.world = world; + this.region = region; + this.perms = perms; + } + + @Override + public Component getComponent(int number) { + if (number == Flags.INBUILT_FLAGS.size()) { + return centerAndBorder(TextComponent.of("Third-Party Flags", TextColor.AQUA)); + } else if (number > Flags.INBUILT_FLAGS.size()) { + number -= 1; + } + Flag flag = FLAGS.get(number); + return createLine(flag, number >= Flags.INBUILT_FLAGS.size()); + } + + @Override + public int getComponentsSize() { + return SIZE; + } + + private Component createLine(Flag flag, boolean thirdParty) { + final TextComponent.Builder builder = TextComponent.builder(""); + + appendFlagName(builder, flag, flag instanceof UnknownFlag ? TextColor.GRAY : thirdParty ? TextColor.LIGHT_PURPLE : TextColor.GOLD); + appendFlagValue(builder, flag); + return builder.build(); + } + + private void appendFlagName(TextComponent.Builder builder, Flag flag, TextColor color) { + final String name = flag.getName(); + int length = monoSpace ? name.length() : FlagFontInfo.getPxLength(name); + builder.append(TextComponent.of(name, color)); + if (flag.usesMembershipAsDefault()) { + builder.append(TextComponent.empty().append(TextComponent.of("*", TextColor.AQUA)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("This is a special flag which defaults to allow for members, and deny for non-members")))); + length += monoSpace ? 1 : FlagFontInfo.getPxLength('*'); + } + if (flag == Flags.PASSTHROUGH) { + builder.append(TextComponent.empty().append(TextComponent.of("*", TextColor.AQUA)) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("This is a special flag which overrides build checks. (Not movement related!)")))); + length += monoSpace ? 1 : FlagFontInfo.getPxLength('*'); + } + int leftover = (monoSpace ? PAD_PX_SIZE / 3 : PAD_PX_SIZE) - length; + builder.append(TextComponent.space()); + leftover -= 4; + if (leftover > 0) { + builder.append(TextComponent.of(Strings.repeat(".", monoSpace ? leftover : leftover / 2), TextColor.DARK_GRAY)); + } + } + + private void appendFlagValue(TextComponent.Builder builder, Flag flag) { + if (flag instanceof StateFlag) { + appendStateFlagValue(builder, (StateFlag) flag); + } else if (flag instanceof BooleanFlag) { + appendBoolFlagValue(builder, ((BooleanFlag) flag)); + } else if (flag instanceof SetFlag) { + appendSetFlagValue(builder, ((SetFlag) flag)); + } else if (flag instanceof RegistryFlag) { + appendRegistryFlagValue(builder, ((RegistryFlag) flag)); + } else if (flag instanceof StringFlag) { + appendStringFlagValue(builder, ((StringFlag) flag)); + } else if (flag instanceof LocationFlag) { + appendLocationFlagValue(builder, ((LocationFlag) flag)); + } else if (flag instanceof NumberFlag) { + appendNumericFlagValue(builder, (NumberFlag) flag); + } else { + String display = String.valueOf(region.getFlag(flag)); + if (display.length() > 23) { + display = display.substring(0, 20) + "..."; + } + appendValueText(builder, flag, display, null); + } + } + + private T getInheritedValue(ProtectedRegion region, Flag flag) { + ProtectedRegion parent = region.getParent(); + T val; + while (parent != null) { + val = parent.getFlag(flag); + if (val != null) { + return val; + } + parent = parent.getParent(); + } + return null; + } + + private void appendValueChoices(TextComponent.Builder builder, Flag flag, Iterator choices, @Nullable String suggestChoice) { + V defVal = flag.getDefault(); + V currVal = region.getFlag(flag); + boolean inherited = false; + if (currVal == null) { + currVal = getInheritedValue(region, flag); + if (currVal != null) { + inherited = true; + } + } + while (choices.hasNext()) { + V choice = choices.next(); + boolean isExplicitSet = currVal == choice && !inherited; + + boolean maySet = perms.maySetFlag(region, flag, isExplicitSet ? null : String.valueOf(choice)); + + TextColor col = isExplicitSet ? TextColor.WHITE : inherited && currVal == choice ? TextColor.GRAY : TextColor.DARK_GRAY; + Set styles = choice == defVal ? ImmutableSet.of(TextDecoration.UNDERLINED) : Collections.emptySet(); + + Component choiceComponent = TextComponent.empty().append(TextComponent.of(capitalize(String.valueOf(choice)), col, styles)); + + List hoverTexts = new ArrayList<>(); + if (maySet) { + if (isExplicitSet) { + hoverTexts.add(TextComponent.of("Click to unset", TextColor.GOLD)); + } else if (DANGER_ZONE.contains(flag) && !(ProtectedRegion.GLOBAL_REGION.equals(region.getId()) && flag == Flags.PASSTHROUGH)) { + hoverTexts.add(TextComponent.of("Setting this flag may have unintended consequences.", TextColor.RED) + .append(TextComponent.newline()) + .append(TextComponent.of("Please read the documentation and set this flag manually if you really intend to.") + .append(TextComponent.newline()) + .append(TextComponent.of("(Hint: You do not need to set this to protect the region!)")))); + } else { + hoverTexts.add(TextComponent.of("Click to set", TextColor.GOLD)); + } + } + Component valType = getToolTipHint(defVal, choice, inherited); + if (valType != null) { + hoverTexts.add(valType); + } + + if (!hoverTexts.isEmpty()) { + TextComponent.Builder hoverBuilder = TextComponent.builder(""); + for (Iterator hovIt = hoverTexts.iterator(); hovIt.hasNext(); ) { + hoverBuilder.append(hovIt.next()); + if (hovIt.hasNext()) { + hoverBuilder.append(TextComponent.newline()); + } + } + choiceComponent = choiceComponent.hoverEvent( + HoverEvent.of(HoverEvent.Action.SHOW_TEXT, hoverBuilder.build())); + } + + if (maySet && (isExplicitSet || !DANGER_ZONE.contains(flag))) { + builder.append(choiceComponent.clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, + makeCommand(flag, isExplicitSet ? "" : choice)))); + } else { + builder.append(choiceComponent); + } + builder.append(TextComponent.space()); + } + if (suggestChoice != null && perms.maySetFlag(region, flag)) { + builder.append(TextComponent.of(suggestChoice, TextColor.DARK_GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Click to set custom value", TextColor.GOLD))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, makeCommand(flag, "")))); + } + } + + private void appendValueText(TextComponent.Builder builder, Flag flag, String display, @Nullable Component hover) { + V defVal = flag.getDefault(); + V currVal = region.getFlag(flag); + boolean inherited = false; + if (currVal == null) { + currVal = getInheritedValue(region, flag); + if (currVal != null) { + inherited = true; + } + } + + boolean isExplicitSet = currVal != null && !inherited; + + boolean maySet = !(flag instanceof UnknownFlag) && perms.maySetFlag(region, flag); + + TextColor col = isExplicitSet ? TextColor.WHITE : inherited ? TextColor.GRAY : TextColor.DARK_GRAY; + Set styles = currVal == defVal ? ImmutableSet.of(TextDecoration.UNDERLINED) : Collections.emptySet(); + + Component displayComponent = TextComponent.empty().append(TextComponent.of(display, col, styles)); + + List hoverTexts = new ArrayList<>(); + if (maySet) { + if (isExplicitSet) { + hoverTexts.add(TextComponent.of("Click to change", TextColor.GOLD)); + } else { + hoverTexts.add(TextComponent.of("Click to set", TextColor.GOLD)); + } + } + Component valType = getToolTipHint(defVal, currVal, inherited); + if (valType != null) { + hoverTexts.add(valType); + } + + if (!hoverTexts.isEmpty()) { + TextComponent.Builder hoverBuilder = TextComponent.builder(""); + for (Iterator hovIt = hoverTexts.iterator(); hovIt.hasNext(); ) { + hoverBuilder.append(hovIt.next()); + if (hovIt.hasNext()) { + hoverBuilder.append(TextComponent.newline()); + } + } + if (hover != null) { + hoverBuilder.append(TextComponent.newline()); + hoverBuilder.append(hover); + } + displayComponent = displayComponent.hoverEvent( + HoverEvent.of(HoverEvent.Action.SHOW_TEXT, hoverBuilder.build())); + } + + if (maySet) { + builder.append(displayComponent.clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + makeCommand(flag, "")))); + } else { + builder.append(displayComponent); + } + builder.append(TextComponent.space()); + } + + private String makeCommand(Flag flag, Object choice) { + return "/rg flag -w \"" + world.getName() + "\" -h " + getCurrentPage() + + " " + region.getId() + " " + flag.getName() + " " + choice; + } + + private String capitalize(String value) { + if (value.isEmpty()) return value; + value = value.toLowerCase(); + return value.length() > 1 + ? Character.toUpperCase(value.charAt(0)) + value.substring(1) + : String.valueOf(Character.toUpperCase(value.charAt(0))); + } + + @Nullable + private Component getToolTipHint(V defVal, V currVal, boolean inherited) { + Component valType; + if (inherited) { + if (currVal == defVal) { + valType = TextComponent.of("Inherited & ") + .append(TextComponent.of("default") + .decoration(TextDecoration.UNDERLINED, true)) + .append(TextComponent.of(" value")); + } else { + valType = TextComponent.of("Inherited value"); + } + } else { + if (currVal == defVal) { + valType = TextComponent.empty() + .append(TextComponent.of("Default") + .decoration(TextDecoration.UNDERLINED, true)) + .append(TextComponent.of(" value")); + } else { + valType = null; + } + } + return valType; + } + + private void appendStateFlagValue(TextComponent.Builder builder, StateFlag flag) { + final Iterator choices = Iterators.forArray(StateFlag.State.values()); + appendValueChoices(builder, flag, choices, null); + } + + private void appendBoolFlagValue(TextComponent.Builder builder, BooleanFlag flag) { + final Iterator choices = Iterators.forArray(Boolean.TRUE, Boolean.FALSE); + appendValueChoices(builder, flag, choices, null); + } + + private void appendSetFlagValue(TextComponent.Builder builder, SetFlag flag) { + Flag subType = flag.getType(); + Class clazz = subType.getClass(); + String subName; + subName = clazz.isAssignableFrom(RegistryFlag.class) + ? ((RegistryFlag) subType).getRegistry().getName() + : subType.getClass().getSimpleName().replace("Flag", ""); + Set currVal = region.getFlag(flag); + if (currVal == null) { + currVal = getInheritedValue(region, flag); + } + String display = (currVal == null ? "" : currVal.size() + "x ") + subName; + final String stringValue = currVal == null ? "" + : currVal.stream().map(String::valueOf).collect(Collectors.joining(",")); + TextComponent hoverComp = TextComponent.of(""); + if (currVal != null) { + hoverComp = hoverComp.append(TextComponent.of("Current values:")) + .append(TextComponent.newline()).append(TextComponent.of(stringValue)); + } + appendValueText(builder, flag, display, hoverComp); + } + + private void appendRegistryFlagValue(TextComponent.Builder builder, RegistryFlag flag) { + final Registry registry = flag.getRegistry(); + String regName = registry.getName(); + Keyed currVal = region.getFlag(flag); + if (currVal == null) { + currVal = getInheritedValue(region, flag); + } + String display = currVal == null ? regName : currVal.getId(); + appendValueText(builder, flag, display, null); + } + + private void appendLocationFlagValue(TextComponent.Builder builder, LocationFlag flag) { + Location currVal = region.getFlag(flag); + if (currVal == null) { + currVal = getInheritedValue(region, flag); + } + if (currVal == null) { + final Location defVal = flag.getDefault(); + if (defVal == null) { + appendValueText(builder, flag, "unset location", null); + } else { + appendValueText(builder, flag, defVal.toString(), TextComponent.of("Default value:") + .append(TextComponent.newline()).append(TextComponent.of(defVal.toString()))); + } + } else { + appendValueText(builder, flag, currVal.toString(), TextComponent.of("Current value:") + .append(TextComponent.newline()).append(TextComponent.of(currVal.toString()))); + } + } + + private void appendNumericFlagValue(TextComponent.Builder builder, NumberFlag flag) { + Number currVal = region.getFlag(flag); + if (currVal == null) { + currVal = getInheritedValue(region, flag); + } + Number defVal = flag.getDefault(); + Number[] suggested = flag.getSuggestedValues(); + SortedSet choices = new TreeSet<>(Comparator.comparing(Number::doubleValue)); + if (currVal != null) { + choices.add(currVal); + } + if (defVal != null) { + choices.add(defVal); + } + if (suggested.length > 0) { + choices.addAll(Arrays.asList(suggested)); + } + //noinspection unchecked + appendValueChoices(builder, flag, (Iterator) choices.iterator(), choices.isEmpty() ? "unset number" : "[custom]"); + } + + private void appendStringFlagValue(TextComponent.Builder builder, StringFlag flag) { + String currVal = region.getFlag(flag); + if (currVal == null) { + currVal = getInheritedValue(region, flag); + } + if (currVal == null) { + final String defVal = flag.getDefault(); + if (defVal == null) { + appendValueText(builder, flag, "unset string", null); + } else { + final TextComponent defComp = LegacyComponentSerializer.INSTANCE.deserialize(defVal); + String display = reduceToText(defComp); + display = display.replace("\n", "\\n"); + if (display.length() > 23) { + display = display.substring(0, 20) + "..."; + } + appendValueText(builder, flag, display, TextComponent.of("Default value:") + .append(TextComponent.newline()).append(defComp)); + } + } else { + TextComponent currComp = LegacyComponentSerializer.INSTANCE.deserialize(currVal); + String display = reduceToText(currComp); + display = display.replace("\n", "\\n"); + if (display.length() > 23) { + display = display.substring(0, 20) + "..."; + } + appendValueText(builder, flag, display, TextComponent.of("Current value:") + .append(TextComponent.newline()).append(currComp)); + } + } + + private static String reduceToText(Component component) { + StringBuilder text = new StringBuilder(); + appendTextTo(text, component); + return text.toString(); + } + + private static void appendTextTo(StringBuilder builder, Component component) { + if (component instanceof TextComponent) { + builder.append(((TextComponent) component).content()); + } else if (component instanceof TranslatableComponent) { + builder.append(((TranslatableComponent) component).key()); + } + for (Component child : component.children()) { + appendTextTo(builder, child); + } + } + + void tryMonoSpacing() { + this.monoSpace = true; + } + + private static final class FlagFontInfo { + static int getPxLength(char c) { + switch (c) { + case 'i': + case ':': + return 1; + case 'l': + return 2; + case '*': + case 't': + return 3; + case 'f': + case 'k': + return 4; + default: + return 5; + } + } + + static int getPxLength(String string) { + return string.chars().reduce(0, (p, i) -> p + getPxLength((char) i) + 1); + } + } +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java new file mode 100644 index 000000000..e41eb1b93 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/MemberCommands.java @@ -0,0 +1,232 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.region; + +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.worldedit.command.util.AsyncCommandBuilder; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.DomainInputResolver; +import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; + +import java.util.concurrent.Callable; + +public class MemberCommands extends RegionCommandsBase { + + private final WorldGuard worldGuard; + + public MemberCommands(WorldGuard worldGuard) { + this.worldGuard = worldGuard; + } + + @Command(aliases = {"addmember", "addmember", "addmem", "am"}, + usage = " ", + flags = "nw:", + desc = "Add a member to a region", + min = 2) + public void addMember(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + String id = args.getString(0); + RegionManager manager = checkRegionManager(world); + ProtectedRegion region = checkExistingRegion(manager, id, true); + + // Check permissions + if (!getPermissionModel(sender).mayAddMembers(region)) { + throw new CommandPermissionsException(); + } + + // Resolve members asynchronously + DomainInputResolver resolver = new DomainInputResolver( + WorldGuard.getInstance().getProfileService(), args.getParsedPaddedSlice(1, 0)); + resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_ONLY); + + + final String description = String.format("Adding members to the region '%s' on '%s'", region.getId(), world.getName()); + AsyncCommandBuilder.wrap(resolver, sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .onSuccess(String.format("Region '%s' updated with new members.", region.getId()), region.getMembers()::addAll) + .onFailure("Failed to add new members", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + + @Command(aliases = {"addowner", "addowner", "ao"}, + usage = " ", + flags = "nw:", + desc = "Add an owner to a region", + min = 2) + public void addOwner(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + + String id = args.getString(0); + + RegionManager manager = checkRegionManager(world); + ProtectedRegion region = checkExistingRegion(manager, id, true); + + // Check permissions + if (!getPermissionModel(sender).mayAddOwners(region)) { + throw new CommandPermissionsException(); + } + + // Resolve owners asynchronously + DomainInputResolver resolver = new DomainInputResolver( + WorldGuard.getInstance().getProfileService(), args.getParsedPaddedSlice(1, 0)); + resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_ONLY); + + + final String description = String.format("Adding owners to the region '%s' on '%s'", region.getId(), world.getName()); + AsyncCommandBuilder.wrap(checkedAddOwners(sender, manager, region, world, resolver), sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .onSuccess(String.format("Region '%s' updated with new owners.", region.getId()), region.getOwners()::addAll) + .onFailure("Failed to add new owners", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + + private static Callable checkedAddOwners(Actor sender, RegionManager manager, ProtectedRegion region, + World world, DomainInputResolver resolver) { + return () -> { + DefaultDomain owners = resolver.call(); + // TODO this was always broken and never checked other players + if (sender instanceof LocalPlayer) { + LocalPlayer player = (LocalPlayer) sender; + if (owners.contains(player) && !sender.hasPermission("worldguard.region.unlimited")) { + int maxRegionCount = WorldGuard.getInstance().getPlatform().getGlobalStateManager() + .get(world).getMaxRegionCount(player); + if (maxRegionCount >= 0 && manager.getRegionCountOfPlayer(player) + >= maxRegionCount) { + throw new CommandException("You already own the maximum allowed amount of regions."); + } + } + } + if (region.getOwners().size() == 0) { + boolean anyOwners = false; + ProtectedRegion parent = region; + while ((parent = parent.getParent()) != null) { + if (parent.getOwners().size() > 0) { + anyOwners = true; + break; + } + } + if (!anyOwners) { + sender.checkPermission("worldguard.region.addowner.unclaimed." + region.getId().toLowerCase()); + } + } + return owners; + }; + } + + @Command(aliases = {"removemember", "remmember", "removemem", "remmem", "rm"}, + usage = " ", + flags = "naw:", + desc = "Remove an owner to a region", + min = 1) + public void removeMember(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + String id = args.getString(0); + RegionManager manager = checkRegionManager(world); + ProtectedRegion region = checkExistingRegion(manager, id, true); + + // Check permissions + if (!getPermissionModel(sender).mayRemoveMembers(region)) { + throw new CommandPermissionsException(); + } + + Callable callable; + if (args.hasFlag('a')) { + callable = region::getMembers; + } else { + if (args.argsLength() < 2) { + throw new CommandException("List some names to remove, or use -a to remove all."); + } + + // Resolve members asynchronously + DomainInputResolver resolver = new DomainInputResolver( + WorldGuard.getInstance().getProfileService(), args.getParsedPaddedSlice(1, 0)); + resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_AND_NAME); + + callable = resolver; + } + + final String description = String.format("Removing members from the region '%s' on '%s'", region.getId(), world.getName()); + AsyncCommandBuilder.wrap(callable, sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .sendMessageAfterDelay("(Please wait... querying player names...)") + .onSuccess(String.format("Region '%s' updated with members removed.", region.getId()), region.getMembers()::removeAll) + .onFailure("Failed to remove members", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + + @Command(aliases = {"removeowner", "remowner", "ro"}, + usage = " ", + flags = "naw:", + desc = "Remove an owner to a region", + min = 1) + public void removeOwner(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + String id = args.getString(0); + RegionManager manager = checkRegionManager(world); + ProtectedRegion region = checkExistingRegion(manager, id, true); + + // Check permissions + if (!getPermissionModel(sender).mayRemoveOwners(region)) { + throw new CommandPermissionsException(); + } + + Callable callable; + if (args.hasFlag('a')) { + callable = region::getOwners; + } else { + if (args.argsLength() < 2) { + throw new CommandException("List some names to remove, or use -a to remove all."); + } + + // Resolve owners asynchronously + DomainInputResolver resolver = new DomainInputResolver( + WorldGuard.getInstance().getProfileService(), args.getParsedPaddedSlice(1, 0)); + resolver.setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_AND_NAME); + + callable = resolver; + } + + final String description = String.format("Removing owners from the region '%s' on '%s'", region.getId(), world.getName()); + AsyncCommandBuilder.wrap(callable, sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .sendMessageAfterDelay("(Please wait... querying player names...)") + .onSuccess(String.format("Region '%s' updated with owners removed.", region.getId()), region.getOwners()::removeAll) + .onFailure("Failed to remove owners", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java new file mode 100644 index 000000000..b0ef7fff9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommands.java @@ -0,0 +1,1216 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.region; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Sets; +import com.sk89q.minecraft.util.commands.Command; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.minecraft.util.commands.CommandPermissionsException; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.command.util.AsyncCommandBuilder; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.component.ErrorFormat; +import com.sk89q.worldedit.util.formatting.component.LabelFormat; +import com.sk89q.worldedit.util.formatting.component.SubtleFormat; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.commands.task.RegionAdder; +import com.sk89q.worldguard.commands.task.RegionLister; +import com.sk89q.worldguard.commands.task.RegionManagerLoader; +import com.sk89q.worldguard.commands.task.RegionManagerSaver; +import com.sk89q.worldguard.commands.task.RegionRemover; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.RegionGroupFlag; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.managers.migration.DriverMigration; +import com.sk89q.worldguard.protection.managers.migration.MigrationException; +import com.sk89q.worldguard.protection.managers.migration.UUIDMigration; +import com.sk89q.worldguard.protection.managers.storage.DriverType; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.util.Enums; +import com.sk89q.worldguard.util.logging.LoggerToChatHandler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Implements the /region commands for WorldGuard. + */ +public final class RegionCommands extends RegionCommandsBase { + + private static final Logger log = Logger.getLogger(RegionCommands.class.getCanonicalName()); + private final WorldGuard worldGuard; + + public RegionCommands(WorldGuard worldGuard) { + checkNotNull(worldGuard); + this.worldGuard = worldGuard; + } + + private static TextComponent passthroughFlagWarning = TextComponent.empty() + .append(TextComponent.of("WARNING:", TextColor.RED, Sets.newHashSet(TextDecoration.BOLD))) + .append(ErrorFormat.wrap(" This flag is unrelated to moving through regions.")) + .append(TextComponent.newline()) + .append(TextComponent.of("It overrides build checks. If you're unsure what this means, see ") + .append(TextComponent.of("[this documentation page]", TextColor.AQUA) + .clickEvent(ClickEvent.of(ClickEvent.Action.OPEN_URL, + "https://worldguard.enginehub.org/en/latest/regions/flags/#overrides"))) + .append(TextComponent.of(" for more info."))); + private static TextComponent buildFlagWarning = TextComponent.empty() + .append(TextComponent.of("WARNING:", TextColor.RED, Sets.newHashSet(TextDecoration.BOLD))) + .append(ErrorFormat.wrap(" Setting this flag is not required for protection.")) + .append(TextComponent.newline()) + .append(TextComponent.of("Setting this flag will completely override default protection, and apply" + + " to members, non-members, pistons, sand physics, and everything else that can modify blocks.")) + .append(TextComponent.newline()) + .append(TextComponent.of("Only set this flag if you are sure you know what you are doing. See ") + .append(TextComponent.of("[this documentation page]", TextColor.AQUA) + .clickEvent(ClickEvent.of(ClickEvent.Action.OPEN_URL, + "https://worldguard.enginehub.org/en/latest/regions/flags/#protection-related"))) + .append(TextComponent.of(" for more info."))); + + /** + * Defines a new region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"define", "def", "d", "create"}, + usage = "[-w ] [ [ []]]", + flags = "ngw:", + desc = "Defines a region", + min = 1) + public void define(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + // Check permissions + if (!getPermissionModel(sender).mayDefine()) { + throw new CommandPermissionsException(); + } + + String id = checkRegionId(args.getString(0), false); + + World world = checkWorld(args, sender, 'w'); + RegionManager manager = checkRegionManager(world); + + checkRegionDoesNotExist(manager, id, true); + + ProtectedRegion region; + + if (args.hasFlag('g')) { + region = new GlobalProtectedRegion(id); + } else { + region = checkRegionFromSelection(sender, id); + } + + RegionAdder task = new RegionAdder(manager, region); + task.addOwnersFromCommand(args, 2); + + final String description = String.format("Adding region '%s'", region.getId()); + AsyncCommandBuilder.wrap(task, sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .onSuccess((Component) null, + t -> { + sender.print(String.format("A new region has been made named '%s'.", region.getId())); + warnAboutDimensions(sender, region); + informNewUser(sender, manager, region); + checkSpawnOverlap(sender, world, region); + }) + .onFailure(String.format("Failed to add the region '%s'", region.getId()), worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + + /** + * Re-defines a region with a new selection. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"redefine", "update", "move"}, + usage = "[-w ] ", + desc = "Re-defines the shape of a region", + flags = "gw:", + min = 1, max = 1) + public void redefine(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + String id = checkRegionId(args.getString(0), false); + + World world = checkWorld(args, sender, 'w'); + RegionManager manager = checkRegionManager(world); + + ProtectedRegion existing = checkExistingRegion(manager, id, false); + + // Check permissions + if (!getPermissionModel(sender).mayRedefine(existing)) { + throw new CommandPermissionsException(); + } + + ProtectedRegion region; + + if (args.hasFlag('g')) { + region = new GlobalProtectedRegion(id); + } else { + region = checkRegionFromSelection(sender, id); + } + + region.copyFrom(existing); + + RegionAdder task = new RegionAdder(manager, region); + + final String description = String.format("Updating region '%s'", region.getId()); + AsyncCommandBuilder.wrap(task, sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .sendMessageAfterDelay("(Please wait... " + description + ")") + .onSuccess((Component) null, + t -> { + sender.print(String.format("Region '%s' has been updated with a new area.", region.getId())); + warnAboutDimensions(sender, region); + informNewUser(sender, manager, region); + checkSpawnOverlap(sender, world, region); + }) + .onFailure(String.format("Failed to update the region '%s'", region.getId()), worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + + /** + * Claiming command for users. + * + *

This command is a joke and it needs to be rewritten. It was contributed + * code :(

+ * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"claim"}, + usage = "", + desc = "Claim a region", + min = 1, max = 1) + public void claim(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + LocalPlayer player = worldGuard.checkPlayer(sender); + RegionPermissionModel permModel = getPermissionModel(player); + + // Check permissions + if (!permModel.mayClaim()) { + throw new CommandPermissionsException(); + } + + String id = checkRegionId(args.getString(0), false); + + RegionManager manager = checkRegionManager(player.getWorld()); + + checkRegionDoesNotExist(manager, id, false); + ProtectedRegion region = checkRegionFromSelection(player, id); + + WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(player.getWorld()); + + // Check whether the player has created too many regions + if (!permModel.mayClaimRegionsUnbounded()) { + int maxRegionCount = wcfg.getMaxRegionCount(player); + if (maxRegionCount >= 0 + && manager.getRegionCountOfPlayer(player) >= maxRegionCount) { + throw new CommandException( + "You own too many regions, delete one first to claim a new one."); + } + } + + ProtectedRegion existing = manager.getRegion(id); + + // Check for an existing region + if (existing != null) { + if (!existing.getOwners().contains(player)) { + throw new CommandException( + "This region already exists and you don't own it."); + } + } + + // We have to check whether this region violates the space of any other reion + ApplicableRegionSet regions = manager.getApplicableRegions(region); + + // Check if this region overlaps any other region + if (regions.size() > 0) { + if (!regions.isOwnerOfAll(player)) { + throw new CommandException("This region overlaps with someone else's region."); + } + } else { + if (wcfg.claimOnlyInsideExistingRegions) { + throw new CommandException("You may only claim regions inside " + + "existing regions that you or your group own."); + } + } + + if (wcfg.maxClaimVolume >= Integer.MAX_VALUE) { + throw new CommandException("The maximum claim volume get in the configuration is higher than is supported. " + + "Currently, it must be " + Integer.MAX_VALUE + " or smaller. Please contact a server administrator."); + } + + // Check claim volume + if (!permModel.mayClaimRegionsUnbounded()) { + if (region instanceof ProtectedPolygonalRegion) { + throw new CommandException("Polygons are currently not supported for /rg claim."); + } + + if (region.volume() > wcfg.maxClaimVolume) { + player.printError("This region is too large to claim."); + player.printError("Max. volume: " + wcfg.maxClaimVolume + ", your volume: " + region.volume()); + return; + } + } + + RegionAdder task = new RegionAdder(manager, region); + task.setLocatorPolicy(UserLocatorPolicy.UUID_ONLY); + task.setOwnersInput(new String[]{player.getName()}); + + final String description = String.format("Claiming region '%s'", id); + AsyncCommandBuilder.wrap(task, sender) + .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), description) + .sendMessageAfterDelay("(Please wait... " + description + ")") + .onSuccess(TextComponent.of(String.format("A new region has been claimed named '%s'.", id)), null) + .onFailure("Failed to claim region", WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + } + + /** + * Get a WorldEdit selection from a region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"select", "sel", "s"}, + usage = "[-w ] [id]", + desc = "Load a region as a WorldEdit selection", + min = 0, max = 1, + flags = "w:") + public void select(CommandContext args, Actor sender) throws CommandException { + World world = checkWorld(args, sender, 'w'); + RegionManager manager = checkRegionManager(world); + ProtectedRegion existing; + + // If no arguments were given, get the region that the player is inside + if (args.argsLength() == 0) { + LocalPlayer player = worldGuard.checkPlayer(sender); + if (!player.getWorld().equals(world)) { // confusing to get current location regions in another world + throw new CommandException("Please specify a region name."); // just don't allow that + } + world = player.getWorld(); + existing = checkRegionStandingIn(manager, player, "/rg select -w \"" + world.getName() + "\" %id%"); + } else { + existing = checkExistingRegion(manager, args.getString(0), false); + } + + // Check permissions + if (!getPermissionModel(sender).maySelect(existing)) { + throw new CommandPermissionsException(); + } + + // Select + setPlayerSelection(sender, existing, world); + } + + /** + * Get information about a region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"info", "i"}, + usage = "[id]", + flags = "usw:", + desc = "Get information about a region", + min = 0, max = 1) + public void info(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + RegionPermissionModel permModel = getPermissionModel(sender); + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + ProtectedRegion existing; + + if (args.argsLength() == 0) { // Get region from where the player is + if (!(sender instanceof LocalPlayer)) { + throw new CommandException("Please specify the region with /region info -w world_name region_name."); + } + + existing = checkRegionStandingIn(manager, (LocalPlayer) sender, true, + "/rg info -w \"" + world.getName() + "\" %id%" + (args.hasFlag('u') ? " -u" : "") + (args.hasFlag('s') ? " -s" : "")); + } else { // Get region from the ID + existing = checkExistingRegion(manager, args.getString(0), true); + } + + // Check permissions + if (!permModel.mayLookup(existing)) { + throw new CommandPermissionsException(); + } + + // Let the player select the region + if (args.hasFlag('s')) { + // Check permissions + if (!permModel.maySelect(existing)) { + throw new CommandPermissionsException(); + } + + setPlayerSelection(worldGuard.checkPlayer(sender), existing, world); + } + + // Print region information + RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), existing, + args.hasFlag('u') ? null : WorldGuard.getInstance().getProfileCache(), sender); + + AsyncCommandBuilder.wrap(printout, sender) + .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), "Fetching region info") + .sendMessageAfterDelay("(Please wait... fetching region information...)") + .onSuccess((Component) null, component -> { + sender.print(component); + checkSpawnOverlap(sender, world, existing); + }) + .onFailure("Failed to fetch region information", WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + } + + /** + * List regions. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"list"}, + usage = "[page]", + desc = "Get a list of regions", + flags = "np:w:", + max = 1) + public void list(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + String ownedBy; + + // Get page + int page = args.getInteger(0, 1); + if (page < 1) { + page = 1; + } + + // -p flag to lookup a player's regions + if (args.hasFlag('p')) { + ownedBy = args.getFlag('p'); + } else { + ownedBy = null; // List all regions + } + + // Check permissions + if (!getPermissionModel(sender).mayList(ownedBy)) { + ownedBy = sender.getName(); // assume they only want their own + if (!getPermissionModel(sender).mayList(ownedBy)) { + throw new CommandPermissionsException(); + } + } + + RegionManager manager = checkRegionManager(world); + + RegionLister task = new RegionLister(manager, sender, world.getName()); + task.setPage(page); + if (ownedBy != null) { + task.filterOwnedByName(ownedBy, args.hasFlag('n')); + } + + AsyncCommandBuilder.wrap(task, sender) + .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), "Getting region list") + .sendMessageAfterDelay("(Please wait... fetching region list...)") + .onFailure("Failed to fetch region list", WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + } + + /** + * Set a flag. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"flag", "f"}, + usage = " [-w world] [-g group] [value]", + flags = "g:w:eh:", + desc = "Set flags", + min = 2) + public void flag(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + String flagName = args.getString(1); + String value = args.argsLength() >= 3 ? args.getJoinedStrings(2) : null; + RegionGroup groupValue = null; + FlagRegistry flagRegistry = WorldGuard.getInstance().getFlagRegistry(); + RegionPermissionModel permModel = getPermissionModel(sender); + + if (args.hasFlag('e')) { + if (value != null) { + throw new CommandException("You cannot use -e(mpty) with a flag value."); + } + + value = ""; + } + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), true); + + // Check permissions + if (!permModel.maySetFlag(existing)) { + throw new CommandPermissionsException(); + } + String regionId = existing.getId(); + + Flag foundFlag = Flags.fuzzyMatchFlag(flagRegistry, flagName); + + // We didn't find the flag, so let's print a list of flags that the user + // can use, and do nothing afterwards + if (foundFlag == null) { + AsyncCommandBuilder.wrap(new FlagListBuilder(flagRegistry, permModel, existing, world, + regionId, sender, flagName), sender) + .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), "Flag list for invalid flag command.") + .onSuccess((Component) null, sender::print) + .onFailure((Component) null, WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + return; + } else if (value != null) { + if (foundFlag == Flags.BUILD || foundFlag == Flags.BLOCK_BREAK || foundFlag == Flags.BLOCK_PLACE) { + sender.print(buildFlagWarning); + if (!sender.isPlayer()) { + sender.printRaw("https://worldguard.enginehub.org/en/latest/regions/flags/#protection-related"); + } + } else if (foundFlag == Flags.PASSTHROUGH) { + sender.print(passthroughFlagWarning); + if (!sender.isPlayer()) { + sender.printRaw("https://worldguard.enginehub.org/en/latest/regions/flags/#overrides"); + } + } + } + + // Also make sure that we can use this flag + // This permission is confusing and probably should be replaced, but + // but not here -- in the model + if (!permModel.maySetFlag(existing, foundFlag, value)) { + throw new CommandPermissionsException(); + } + + // -g for group flag + if (args.hasFlag('g')) { + String group = args.getFlag('g'); + RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); + + if (groupFlag == null) { + throw new CommandException("Region flag '" + foundFlag.getName() + + "' does not have a group flag!"); + } + + // Parse the [-g group] separately so entire command can abort if parsing + // the [value] part throws an error. + try { + groupValue = groupFlag.parseInput(FlagContext.create().setSender(sender).setInput(group).setObject("region", existing).build()); + } catch (InvalidFlagFormat e) { + throw new CommandException(e.getMessage()); + } + + } + + // Set the flag value if a value was set + if (value != null) { + // Set the flag if [value] was given even if [-g group] was given as well + try { + value = setFlag(existing, foundFlag, sender, value).toString(); + } catch (InvalidFlagFormat e) { + throw new CommandException(e.getMessage()); + } + + if (!args.hasFlag('h')) { + sender.print("Region flag " + foundFlag.getName() + " set on '" + regionId + "' to '" + value + "'."); + } + + // No value? Clear the flag, if -g isn't specified + } else if (!args.hasFlag('g')) { + // Clear the flag only if neither [value] nor [-g group] was given + existing.setFlag(foundFlag, null); + + // Also clear the associated group flag if one exists + RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); + if (groupFlag != null) { + existing.setFlag(groupFlag, null); + } + + if (!args.hasFlag('h')) { + sender.print("Region flag " + foundFlag.getName() + " removed from '" + regionId + "'. (Any -g(roups) were also removed.)"); + } + } + + // Now set the group + if (groupValue != null) { + RegionGroupFlag groupFlag = foundFlag.getRegionGroupFlag(); + + // If group set to the default, then clear the group flag + if (groupValue == groupFlag.getDefault()) { + existing.setFlag(groupFlag, null); + sender.print("Region group flag for '" + foundFlag.getName() + "' reset to default."); + } else { + existing.setFlag(groupFlag, groupValue); + sender.print("Region group flag for '" + foundFlag.getName() + "' set."); + } + } + + // Print region information + if (args.hasFlag('h')) { + int page = args.getFlagInteger('h'); + sendFlagHelper(sender, world, existing, permModel, page); + } else { + RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), existing, null, sender); + printout.append(SubtleFormat.wrap("(Current flags: ")); + printout.appendFlagsList(false); + printout.append(SubtleFormat.wrap(")")); + printout.send(sender); + checkSpawnOverlap(sender, world, existing); + } + } + + @Command(aliases = "flags", + usage = "[-p ] [id]", + flags = "p:w:", + desc = "View region flags", + min = 0, max = 2) + public void flagHelper(CommandContext args, Actor sender) throws CommandException { + World world = checkWorld(args, sender, 'w'); // Get the world + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + ProtectedRegion region; + if (args.argsLength() == 0) { // Get region from where the player is + if (!(sender instanceof LocalPlayer)) { + throw new CommandException("Please specify the region with /region flags -w world_name region_name."); + } + + region = checkRegionStandingIn(manager, (LocalPlayer) sender, true, + "/rg flags -w \"" + world.getName() + "\" %id%"); + } else { // Get region from the ID + region = checkExistingRegion(manager, args.getString(0), true); + } + + final RegionPermissionModel perms = getPermissionModel(sender); + if (!perms.mayLookup(region)) { + throw new CommandPermissionsException(); + } + int page = args.hasFlag('p') ? args.getFlagInteger('p') : 1; + + sendFlagHelper(sender, world, region, perms, page); + } + + private static void sendFlagHelper(Actor sender, World world, ProtectedRegion region, RegionPermissionModel perms, int page) { + final FlagHelperBox flagHelperBox = new FlagHelperBox(world, region, perms); + flagHelperBox.setComponentsPerPage(18); + if (!sender.isPlayer()) { + flagHelperBox.tryMonoSpacing(); + } + AsyncCommandBuilder.wrap(() -> { + if (checkSpawnOverlap(sender, world, region)) { + flagHelperBox.setComponentsPerPage(15); + } + return flagHelperBox.create(page); + }, sender) + .onSuccess((Component) null, sender::print) + .onFailure("Failed to get region flags", WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + } + + /** + * Set the priority of a region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"setpriority", "priority", "pri"}, + usage = " ", + flags = "w:", + desc = "Set the priority of a region", + min = 2, max = 2) + public void setPriority(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + int priority = args.getInteger(1); + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), false); + + // Check permissions + if (!getPermissionModel(sender).maySetPriority(existing)) { + throw new CommandPermissionsException(); + } + + existing.setPriority(priority); + + sender.print("Priority of '" + existing.getId() + "' set to " + priority + " (higher numbers override)."); + checkSpawnOverlap(sender, world, existing); + } + + /** + * Set the parent of a region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"setparent", "parent", "par"}, + usage = " [parent-id]", + flags = "w:", + desc = "Set the parent of a region", + min = 1, max = 2) + public void setParent(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + ProtectedRegion parent; + ProtectedRegion child; + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + + // Get parent and child + child = checkExistingRegion(manager, args.getString(0), false); + if (args.argsLength() == 2) { + parent = checkExistingRegion(manager, args.getString(1), false); + } else { + parent = null; + } + + // Check permissions + if (!getPermissionModel(sender).maySetParent(child, parent)) { + throw new CommandPermissionsException(); + } + + try { + child.setParent(parent); + } catch (CircularInheritanceException e) { + // Tell the user what's wrong + RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), parent, null, sender); + assert parent != null; + printout.append(ErrorFormat.wrap("Uh oh! Setting '", parent.getId(), "' to be the parent of '", child.getId(), + "' would cause circular inheritance.")).newline(); + printout.append(SubtleFormat.wrap("(Current inheritance on '", parent.getId(), "':")).newline(); + printout.appendParentTree(true); + printout.append(SubtleFormat.wrap(")")); + printout.send(sender); + return; + } + + // Tell the user the current inheritance + RegionPrintoutBuilder printout = new RegionPrintoutBuilder(world.getName(), child, null, sender); + printout.append(TextComponent.of("Inheritance set for region '" + child.getId() + "'.", TextColor.LIGHT_PURPLE)); + if (parent != null) { + printout.newline(); + printout.append(SubtleFormat.wrap("(Current inheritance:")).newline(); + printout.appendParentTree(true); + printout.append(SubtleFormat.wrap(")")); + } else { + printout.append(LabelFormat.wrap(" Region is now orphaned.")); + } + printout.send(sender); + } + + /** + * Remove a region. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"remove", "delete", "del", "rem"}, + usage = "", + flags = "fuw:", + desc = "Remove a region", + min = 1, max = 1) + public void remove(CommandContext args, Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = checkWorld(args, sender, 'w'); // Get the world + boolean removeChildren = args.hasFlag('f'); + boolean unsetParent = args.hasFlag('u'); + + // Lookup the existing region + RegionManager manager = checkRegionManager(world); + ProtectedRegion existing = checkExistingRegion(manager, args.getString(0), true); + + // Check permissions + if (!getPermissionModel(sender).mayDelete(existing)) { + throw new CommandPermissionsException(); + } + + RegionRemover task = new RegionRemover(manager, existing); + + if (removeChildren && unsetParent) { + throw new CommandException("You cannot use both -u (unset parent) and -f (remove children) together."); + } else if (removeChildren) { + task.setRemovalStrategy(RemovalStrategy.REMOVE_CHILDREN); + } else if (unsetParent) { + task.setRemovalStrategy(RemovalStrategy.UNSET_PARENT_IN_CHILDREN); + } + + final String description = String.format("Removing region '%s' in '%s'", existing.getId(), world.getName()); + AsyncCommandBuilder.wrap(task, sender) + .registerWithSupervisor(WorldGuard.getInstance().getSupervisor(), description) + .sendMessageAfterDelay("Please wait... removing region.") + .onSuccess((Component) null, removed -> sender.print(TextComponent.of( + "Successfully removed " + removed.stream().map(ProtectedRegion::getId).collect(Collectors.joining(", ")) + ".", + TextColor.LIGHT_PURPLE))) + .onFailure("Failed to remove region", WorldGuard.getInstance().getExceptionConverter()) + .buildAndExec(WorldGuard.getInstance().getExecutorService()); + } + + /** + * Reload the region database. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"load", "reload"}, + usage = "[world]", + desc = "Reload regions from file", + flags = "w:") + public void load(CommandContext args, final Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = null; + try { + world = checkWorld(args, sender, 'w'); // Get the world + } catch (CommandException ignored) { + // assume the user wants to reload all worlds + } + + // Check permissions + if (!getPermissionModel(sender).mayForceLoadRegions()) { + throw new CommandPermissionsException(); + } + + if (world != null) { + RegionManager manager = checkRegionManager(world); + + if (manager == null) { + throw new CommandException("No region manager exists for world '" + world.getName() + "'."); + } + + final String description = String.format("Loading region data for '%s'.", world.getName()); + AsyncCommandBuilder.wrap(new RegionManagerLoader(manager), sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .sendMessageAfterDelay("Please wait... " + description) + .onSuccess(String.format("Loaded region data for '%s'", world.getName()), null) + .onFailure(String.format("Failed to load region data for '%s'", world.getName()), worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } else { + // Load regions for all worlds + List managers = new ArrayList<>(); + + for (World w : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(w); + if (manager != null) { + managers.add(manager); + } + } + + AsyncCommandBuilder.wrap(new RegionManagerLoader(managers), sender) + .registerWithSupervisor(worldGuard.getSupervisor(), "Loading regions for all worlds") + .sendMessageAfterDelay("(Please wait... loading region data for all worlds...)") + .onSuccess("Successfully load the region data for all worlds.", null) + .onFailure("Failed to load regions for all worlds", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + } + + /** + * Re-save the region database. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"save", "write"}, + usage = "[world]", + desc = "Re-save regions to file", + flags = "w:") + public void save(CommandContext args, final Actor sender) throws CommandException { + warnAboutSaveFailures(sender); + + World world = null; + try { + world = checkWorld(args, sender, 'w'); // Get the world + } catch (CommandException ignored) { + // assume user wants to save all worlds + } + + // Check permissions + if (!getPermissionModel(sender).mayForceSaveRegions()) { + throw new CommandPermissionsException(); + } + + if (world != null) { + RegionManager manager = checkRegionManager(world); + + if (manager == null) { + throw new CommandException("No region manager exists for world '" + world.getName() + "'."); + } + + final String description = String.format("Saving region data for '%s'.", world.getName()); + AsyncCommandBuilder.wrap(new RegionManagerSaver(manager), sender) + .registerWithSupervisor(worldGuard.getSupervisor(), description) + .sendMessageAfterDelay("Please wait... " + description) + .onSuccess(String.format("Saving region data for '%s'", world.getName()), null) + .onFailure(String.format("Failed to save region data for '%s'", world.getName()), worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } else { + // Save for all worlds + List managers = new ArrayList<>(); + + final RegionContainer regionContainer = worldGuard.getPlatform().getRegionContainer(); + for (World w : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + RegionManager manager = regionContainer.get(w); + if (manager != null) { + managers.add(manager); + } + } + + AsyncCommandBuilder.wrap(new RegionManagerSaver(managers), sender) + .registerWithSupervisor(worldGuard.getSupervisor(), "Saving regions for all worlds") + .sendMessageAfterDelay("(Please wait... saving region data for all worlds...)") + .onSuccess("Successfully saved the region data for all worlds.", null) + .onFailure("Failed to save regions for all worlds", worldGuard.getExceptionConverter()) + .buildAndExec(worldGuard.getExecutorService()); + } + } + + /** + * Migrate the region database. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"migratedb"}, usage = " ", + flags = "y", + desc = "Migrate from one Protection Database to another.", min = 2, max = 2) + public void migrateDB(CommandContext args, Actor sender) throws CommandException { + // Check permissions + if (!getPermissionModel(sender).mayMigrateRegionStore()) { + throw new CommandPermissionsException(); + } + + DriverType from = Enums.findFuzzyByValue(DriverType.class, args.getString(0)); + DriverType to = Enums.findFuzzyByValue(DriverType.class, args.getString(1)); + + if (from == null) { + throw new CommandException("The value of 'from' is not a recognized type of region data database."); + } + + if (to == null) { + throw new CommandException("The value of 'to' is not a recognized type of region region data database."); + } + + if (from == to) { + throw new CommandException("It is not possible to migrate between the same types of region data databases."); + } + + if (!args.hasFlag('y')) { + throw new CommandException("This command is potentially dangerous.\n" + + "Please ensure you have made a backup of your data, and then re-enter the command with -y tacked on at the end to proceed."); + } + + ConfigurationManager config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + RegionDriver fromDriver = config.regionStoreDriverMap.get(from); + RegionDriver toDriver = config.regionStoreDriverMap.get(to); + + if (fromDriver == null) { + throw new CommandException("The driver specified as 'from' does not seem to be supported in your version of WorldGuard."); + } + + if (toDriver == null) { + throw new CommandException("The driver specified as 'to' does not seem to be supported in your version of WorldGuard."); + } + + DriverMigration migration = new DriverMigration(fromDriver, toDriver, WorldGuard.getInstance().getFlagRegistry()); + + LoggerToChatHandler handler = null; + Logger minecraftLogger = null; + + if (sender instanceof LocalPlayer) { + handler = new LoggerToChatHandler(sender); + handler.setLevel(Level.ALL); + minecraftLogger = Logger.getLogger("com.sk89q.worldguard"); + minecraftLogger.addHandler(handler); + } + + try { + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + sender.print("Now performing migration... this may take a while."); + container.migrate(migration); + sender.print( + "Migration complete! This only migrated the data. If you already changed your settings to use " + + "the target driver, then WorldGuard is now using the new data. If not, you have to adjust your " + + "configuration to use the new driver and then restart your server."); + } catch (MigrationException e) { + log.log(Level.WARNING, "Failed to migrate", e); + throw new CommandException("Error encountered while migrating: " + e.getMessage()); + } finally { + if (minecraftLogger != null) { + minecraftLogger.removeHandler(handler); + } + } + } + + /** + * Migrate the region databases to use UUIDs rather than name. + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"migrateuuid"}, + desc = "Migrate loaded databases to use UUIDs", max = 0) + public void migrateUuid(CommandContext args, Actor sender) throws CommandException { + // Check permissions + if (!getPermissionModel(sender).mayMigrateRegionNames()) { + throw new CommandPermissionsException(); + } + + LoggerToChatHandler handler = null; + Logger minecraftLogger = null; + + if (sender instanceof LocalPlayer) { + handler = new LoggerToChatHandler(sender); + handler.setLevel(Level.ALL); + minecraftLogger = Logger.getLogger("com.sk89q.worldguard"); + minecraftLogger.addHandler(handler); + } + + try { + ConfigurationManager config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + RegionDriver driver = container.getDriver(); + UUIDMigration migration = new UUIDMigration(driver, WorldGuard.getInstance().getProfileService(), WorldGuard.getInstance().getFlagRegistry()); + migration.setKeepUnresolvedNames(config.keepUnresolvedNames); + sender.print("Now performing migration... this may take a while."); + container.migrate(migration); + sender.print("Migration complete!"); + } catch (MigrationException e) { + log.log(Level.WARNING, "Failed to migrate", e); + throw new CommandException("Error encountered while migrating: " + e.getMessage()); + } finally { + if (minecraftLogger != null) { + minecraftLogger.removeHandler(handler); + } + } + } + + /** + * Teleport to a region + * + * @param args the arguments + * @param sender the sender + * @throws CommandException any error + */ + @Command(aliases = {"teleport", "tp"}, + usage = "", + flags = "sw:", + desc = "Teleports you to the location associated with the region.", + min = 1, max = 1) + public void teleport(CommandContext args, Actor sender) throws CommandException { + LocalPlayer player = worldGuard.checkPlayer(sender); + Location teleportLocation; + + // Lookup the existing region + World world = checkWorld(args, player, 'w'); + RegionManager regionManager = checkRegionManager(world); + ProtectedRegion existing = checkExistingRegion(regionManager, args.getString(0), true); + + // Check permissions + if (!getPermissionModel(player).mayTeleportTo(existing)) { + throw new CommandPermissionsException(); + } + + // -s for spawn location + if (args.hasFlag('s')) { + teleportLocation = FlagValueCalculator.getEffectiveFlagOf(existing, Flags.SPAWN_LOC, player); + + if (teleportLocation == null) { + throw new CommandException( + "The region has no spawn point associated."); + } + } else { + teleportLocation = FlagValueCalculator.getEffectiveFlagOf(existing, Flags.TELE_LOC, player); + + if (teleportLocation == null) { + throw new CommandException("The region has no teleport point associated."); + } + } + + String message = FlagValueCalculator.getEffectiveFlagOf(existing, Flags.TELE_MESSAGE, player); + + // If the flag isn't set, use the default message + // If message.isEmpty(), no message is sent by LocalPlayer#teleport(...) + if (message == null) { + message = Flags.TELE_MESSAGE.getDefault(); + } + + player.teleport(teleportLocation, + message.replace("%id%", existing.getId()), + "Unable to teleport to region '" + existing.getId() + "'."); + } + + @Command(aliases = {"toggle-bypass", "bypass"}, + usage = "[on|off]", + desc = "Toggle region bypassing, effectively ignoring bypass permissions.") + public void toggleBypass(CommandContext args, Actor sender) throws CommandException { + LocalPlayer player = worldGuard.checkPlayer(sender); + if (!player.hasPermission("worldguard.region.toggle-bypass")) { + throw new CommandPermissionsException(); + } + Session session = WorldGuard.getInstance().getPlatform().getSessionManager().get(player); + boolean shouldEnableBypass; + if (args.argsLength() > 0) { + String arg1 = args.getString(0); + if (!arg1.equalsIgnoreCase("on") && !arg1.equalsIgnoreCase("off")) { + throw new CommandException("Allowed optional arguments are: on, off"); + } + shouldEnableBypass = arg1.equalsIgnoreCase("on"); + } else { + shouldEnableBypass = session.hasBypassDisabled(); + } + if (shouldEnableBypass) { + session.setBypassDisabled(false); + player.print("You are now bypassing region protection (as long as you have permission)."); + } else { + session.setBypassDisabled(true); + player.print("You are no longer bypassing region protection."); + } + } + + private static class FlagListBuilder implements Callable { + private final FlagRegistry flagRegistry; + private final RegionPermissionModel permModel; + private final ProtectedRegion existing; + private final World world; + private final String regionId; + private final Actor sender; + private final String flagName; + + FlagListBuilder(FlagRegistry flagRegistry, RegionPermissionModel permModel, ProtectedRegion existing, + World world, String regionId, Actor sender, String flagName) { + this.flagRegistry = flagRegistry; + this.permModel = permModel; + this.existing = existing; + this.world = world; + this.regionId = regionId; + this.sender = sender; + this.flagName = flagName; + } + + @Override + public Component call() { + ArrayList flagList = new ArrayList<>(); + + // Need to build a list + for (Flag flag : flagRegistry) { + // Can the user set this flag? + if (!permModel.maySetFlag(existing, flag)) { + continue; + } + + flagList.add(flag.getName()); + } + + Collections.sort(flagList); + + final TextComponent.Builder builder = TextComponent.builder("Available flags: "); + + final HoverEvent clickToSet = HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to set")); + for (int i = 0; i < flagList.size(); i++) { + String flag = flagList.get(i); + + builder.append(TextComponent.of(flag, i % 2 == 0 ? TextColor.GRAY : TextColor.WHITE) + .hoverEvent(clickToSet).clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + "/rg flag -w \"" + world.getName() + "\" " + regionId + " " + flag + " "))); + if (i < flagList.size() + 1) { + builder.append(TextComponent.of(", ")); + } + } + + Component ret = ErrorFormat.wrap("Unknown flag specified: " + flagName) + .append(TextComponent.newline()) + .append(builder.build()); + if (sender.isPlayer()) { + return ret.append(TextComponent.of("Or use the command ", TextColor.LIGHT_PURPLE) + .append(TextComponent.of("/rg flags " + regionId, TextColor.AQUA) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, + "/rg flags -w \"" + world.getName() + "\" " + regionId)))); + } + return ret; + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java new file mode 100644 index 000000000..9274f2eb2 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionCommandsBase.java @@ -0,0 +1,429 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.region; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.regions.selector.Polygonal2DRegionSelector; +import com.sk89q.worldedit.util.formatting.component.ErrorFormat; +import com.sk89q.worldedit.util.formatting.component.SubtleFormat; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption; +import com.sk89q.worldguard.protection.util.WorldEditRegionConverter; + +import java.util.Set; +import java.util.stream.Collectors; + +class RegionCommandsBase { + + protected RegionCommandsBase() { + } + + /** + * Get the permission model to lookup permissions. + * + * @param sender the sender + * @return the permission model + */ + protected static RegionPermissionModel getPermissionModel(Actor sender) { + return new RegionPermissionModel(sender); + } + + /** + * Gets the world from the given flag, or falling back to the the current player + * if the sender is a player, otherwise reporting an error. + * + * @param args the arguments + * @param sender the sender + * @param flag the flag (such as 'w') + * @return a world + * @throws CommandException on error + */ + protected static World checkWorld(CommandContext args, Actor sender, char flag) throws CommandException { + return checkWorld(args, sender, flag, true); + } + + protected static World checkWorld(CommandContext args, Actor sender, char flag, boolean allowWorldEditOverride) throws CommandException { + if (args.hasFlag(flag)) { + return WorldGuard.getInstance().getPlatform().getMatcher().matchWorld(sender, args.getFlag(flag)); + } else { + if (allowWorldEditOverride) { + try { + World override = WorldEdit.getInstance().getSessionManager().get(sender).getWorldOverride(); + if (override != null) { + if (sender instanceof LocalPlayer && !override.equals(((LocalPlayer) sender).getWorld())) { + sender.printDebug(TextComponent.of("Using //world override for region command: " + override.getName())); + } + return override; + } + } catch (NoSuchMethodError ignored) { + } + } + if (sender instanceof LocalPlayer) { + return ((LocalPlayer) sender).getWorld(); + } else { + throw new CommandException("Please specify " + "the world with -" + flag + " world_name."); + } + } + } + + /** + * Validate a region ID. + * + * @param id the id + * @param allowGlobal whether __global__ is allowed + * @return the id given + * @throws CommandException thrown on an error + */ + protected static String checkRegionId(String id, boolean allowGlobal) throws CommandException { + if (!ProtectedRegion.isValidId(id)) { + throw new CommandException( + "The region name of '" + id + "' contains characters that are not allowed."); + } + + if (!allowGlobal && id.equalsIgnoreCase("__global__")) { // Sorry, no global + throw new CommandException( + "Sorry, you can't use __global__ here."); + } + + return id; + } + + /** + * Get a protected region by a given name, otherwise throw a + * {@link CommandException}. + * + *

This also validates the region ID.

+ * + * @param regionManager the region manager + * @param id the name to search + * @param allowGlobal true to allow selecting __global__ + * @throws CommandException thrown if no region is found by the given name + */ + protected static ProtectedRegion checkExistingRegion(RegionManager regionManager, String id, boolean allowGlobal) throws CommandException { + // Validate the id + checkRegionId(id, allowGlobal); + + ProtectedRegion region = regionManager.getRegion(id); + + // No region found! + if (region == null) { + // But we want a __global__, so let's create one + if (id.equalsIgnoreCase("__global__")) { + region = new GlobalProtectedRegion(id); + regionManager.addRegion(region); + return region; + } + + throw new CommandException( + "No region could be found with the name of '" + id + "'."); + } + + return region; + } + + + /** + * Get the region at the player's location, if possible. + * + *

If the player is standing in several regions, an error will be raised + * and a list of regions will be provided.

+ * + * @param regionManager the region manager + * @param player the player + * @return a region + * @throws CommandException thrown if no region was found + */ + protected static ProtectedRegion checkRegionStandingIn(RegionManager regionManager, LocalPlayer player, String rgCmd) throws CommandException { + return checkRegionStandingIn(regionManager, player, false, rgCmd); + } + + /** + * Get the region at the player's location, if possible. + * + *

If the player is standing in several regions, an error will be raised + * and a list of regions will be provided.

+ * + *

If the player is not standing in any regions, the global region will + * returned if allowGlobal is true and it exists.

+ * + * @param regionManager the region manager + * @param player the player + * @param allowGlobal whether to search for a global region if no others are found + * @return a region + * @throws CommandException thrown if no region was found + */ + protected static ProtectedRegion checkRegionStandingIn(RegionManager regionManager, LocalPlayer player, boolean allowGlobal, String rgCmd) throws CommandException { + ApplicableRegionSet set = regionManager.getApplicableRegions(player.getLocation().toVector().toBlockPoint(), QueryOption.SORT); + + if (set.size() == 0) { + if (allowGlobal) { + ProtectedRegion global = checkExistingRegion(regionManager, "__global__", true); + player.printDebug("You're not standing in any " + + "regions. Using the global region for this world instead."); + return global; + } + throw new CommandException( + "You're not standing in a region." + + "Specify an ID if you want to select a specific region."); + } else if (set.size() > 1) { + boolean first = true; + + final TextComponent.Builder builder = TextComponent.builder(""); + builder.append(TextComponent.of("Current regions: ", TextColor.GOLD)); + for (ProtectedRegion region : set) { + if (!first) { + builder.append(TextComponent.of(", ")); + } + first = false; + TextComponent regionComp = TextComponent.of(region.getId(), TextColor.AQUA); + if (rgCmd != null && rgCmd.contains("%id%")) { + regionComp = regionComp.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to pick this region"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, rgCmd.replace("%id%", region.getId()))); + } + builder.append(regionComp); + } + player.print(builder.build()); + throw new CommandException("You're standing in several regions (please pick one)."); + } + + return set.iterator().next(); + } + + /** + * Get a WorldEdit selection for an actor, or emit an exception if there is none + * available. + * + * @param actor the actor + * @return the selection + * @throws CommandException thrown on an error + */ + protected static Region checkSelection(Actor actor) throws CommandException { + LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(actor); + try { + if (localSession == null || localSession.getSelectionWorld() == null) { + throw new IncompleteRegionException(); + } + return localSession.getRegionSelector(localSession.getSelectionWorld()).getRegion(); + } catch (IncompleteRegionException e) { + throw new CommandException("Please select an area first. " + + "Use WorldEdit to make a selection! " + + "(see: https://worldedit.enginehub.org/en/latest/usage/regions/selections/)."); + } + } + + /** + * Check that a region with the given ID does not already exist. + * + * @param manager the manager + * @param id the ID + * @throws CommandException thrown if the ID already exists + */ + protected static void checkRegionDoesNotExist(RegionManager manager, String id, boolean mayRedefine) throws CommandException { + if (manager.hasRegion(id)) { + throw new CommandException("A region with that name already exists. Please choose another name." + + (mayRedefine ? " To change the shape, use /region redefine " + id + "." : "")); + } + } + + /** + * Check that the given region manager is not null. + * + * @param world the world + * @throws CommandException thrown if the manager is null + */ + protected static RegionManager checkRegionManager(World world) throws CommandException { + if (!WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world).useRegions) { + throw new CommandException("Region support is disabled in the target world. " + + "It can be enabled per-world in WorldGuard's configuration files. " + + "However, you may need to restart your server afterwards."); + } + + RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(world); + if (manager == null) { + throw new CommandException("Region data failed to load for this world. " + + "Please ask a server administrator to read the logs to identify the reason."); + } + return manager; + } + + /** + * Create a {@link ProtectedRegion} from the actor's selection. + * + * @param actor the actor + * @param id the ID of the new region + * @return a new region + * @throws CommandException thrown on an error + */ + protected static ProtectedRegion checkRegionFromSelection(Actor actor, String id) throws CommandException { + Region selection = checkSelection(actor); + + // Detect the type of region from WorldEdit + if (selection instanceof Polygonal2DRegion) { + Polygonal2DRegion polySel = (Polygonal2DRegion) selection; + int minY = polySel.getMinimumPoint().getBlockY(); + int maxY = polySel.getMaximumPoint().getBlockY(); + return new ProtectedPolygonalRegion(id, polySel.getPoints(), minY, maxY); + } else if (selection instanceof CuboidRegion) { + BlockVector3 min = selection.getMinimumPoint(); + BlockVector3 max = selection.getMaximumPoint(); + return new ProtectedCuboidRegion(id, min, max); + } else { + throw new CommandException("Sorry, you can only use cuboids and polygons for WorldGuard regions."); + } + } + + /** + * Warn the region saving is failing. + * + * @param sender the sender to send the message to + */ + protected static void warnAboutSaveFailures(Actor sender) { + RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer(); + Set failures = container.getSaveFailures(); + + if (!failures.isEmpty()) { + String failingList = Joiner.on(", ").join(failures.stream() + .map(regionManager -> "'" + regionManager.getName() + "'").collect(Collectors.toList())); + + sender.print(TextComponent.of("(Warning: The background saving of region data is failing for these worlds: " + failingList + ". " + + "Your changes are getting lost. See the server log for more information.)", TextColor.GOLD)); + } + } + + /** + * Warn the sender if the dimensions of the given region are worrying. + * + * @param sender the sender to send the message to + * @param region the region + */ + protected static void warnAboutDimensions(Actor sender, ProtectedRegion region) { + if (region instanceof GlobalProtectedRegion) { + return; + } + int height = region.getMaximumPoint().getBlockY() - region.getMinimumPoint().getBlockY(); + if (height <= 2) { + sender.printDebug("(Warning: The height of the region was " + (height + 1) + " block(s).)"); + } + } + + /** + * Inform a new user about automatic protection. + * + * @param sender the sender to send the message to + * @param manager the region manager + * @param region the region + */ + protected static void informNewUser(Actor sender, RegionManager manager, ProtectedRegion region) { + if (manager.size() <= 2) { + sender.print(SubtleFormat.wrap("(This region is NOW PROTECTED from modification from others. Don't want that? Use ") + .append(TextComponent.of("/rg flag " + region.getId() + " passthrough allow", TextColor.AQUA)) + .append(TextComponent.of(")", TextColor.GRAY))); + } + } + + /** + * Inform a user if the region overlaps spawn protection. + * + * @param sender the sender to send the message to + * @param world the world the region is in + * @param region the region + */ + protected static boolean checkSpawnOverlap(Actor sender, World world, ProtectedRegion region) { + ProtectedRegion spawn = WorldGuard.getInstance().getPlatform().getSpawnProtection(world); + if (spawn != null) { + if (!spawn.getIntersectingRegions(ImmutableList.of(region)).isEmpty()) { + sender.print(ErrorFormat.wrap("Warning!") + .append(TextComponent.of(" This region overlaps vanilla's spawn protection. WorldGuard cannot " + + "override this, and only server operators will be able to interact with this area.", TextColor.WHITE))); + return true; + } + } + return false; + } + + /** + * Set an actor's selection to a given region. + * + * @param actor the actor + * @param region the region + * @throws CommandException thrown on a command error + */ + protected static void setPlayerSelection(Actor actor, ProtectedRegion region, World world) throws CommandException { + LocalSession session = WorldEdit.getInstance().getSessionManager().get(actor); + + RegionSelector selector = WorldEditRegionConverter.convertToSelector(region); + if (selector != null) { + selector.setWorld(world); + session.setRegionSelector(world, selector); + selector.explainRegionAdjust(actor, session); + actor.print("Region selected as " + region.getType().getName()); + } else { + throw new CommandException("Can't select that region! " + + "The region type '" + region.getType().getName() + "' can't be selected."); + } + } + + /** + * Utility method to set a flag. + * + * @param region the region + * @param flag the flag + * @param sender the sender + * @param value the value + * @throws InvalidFlagFormat thrown if the value is invalid + */ + protected static V setFlag(ProtectedRegion region, Flag flag, Actor sender, String value) throws InvalidFlagFormat { + V val = flag.parseInput(FlagContext.create().setSender(sender).setInput(value).setObject("region", region).build()); + region.setFlag(flag, val); + return val; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java new file mode 100644 index 000000000..d5d4e9d8a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/region/RegionPrintoutBuilder.java @@ -0,0 +1,391 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.region; + +import com.sk89q.worldguard.protection.flags.registry.UnknownFlag; +import com.sk89q.worldguard.util.profile.cache.ProfileCache; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.component.ErrorFormat; +import com.sk89q.worldedit.util.formatting.component.LabelFormat; +import com.sk89q.worldedit.util.formatting.component.MessageBox; +import com.sk89q.worldedit.util.formatting.component.SubtleFormat; +import com.sk89q.worldedit.util.formatting.component.TextComponentProducer; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroupFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.Callable; + +import javax.annotation.Nullable; + +/** + * Create a region printout, as used in /region info to show information about + * a region. + */ +public class RegionPrintoutBuilder implements Callable { + + private final String world; + private final ProtectedRegion region; + @Nullable + private final ProfileCache cache; + private final TextComponentProducer builder = new TextComponentProducer(); + private final RegionPermissionModel perms; + + /** + * Create a new instance with a region to report on. + * + * @param region the region + * @param cache a profile cache, or {@code null} + */ + public RegionPrintoutBuilder(String world, ProtectedRegion region, @Nullable ProfileCache cache, @Nullable Actor actor) { + this.world = world; + this.region = region; + this.cache = cache; + this.perms = actor != null && actor.isPlayer() ? new RegionPermissionModel(actor) : null; + } + + /** + * Add a new line. + */ + public void newline() { + builder.append(TextComponent.newline()); + } + + /** + * Add region name, type, and priority. + */ + public void appendBasics() { + builder.append(TextComponent.of("Region: ", TextColor.BLUE)); + builder.append(TextComponent.of(region.getId(), TextColor.YELLOW) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg info -w \"" + world + "\" " + region.getId()))); + + builder.append(TextComponent.of(" (type=", TextColor.GRAY)); + builder.append(TextComponent.of(region.getType().getName())); + + builder.append(TextComponent.of(", priority=", TextColor.GRAY)); + appendPriorityComponent(region); + builder.append(TextComponent.of(")", TextColor.GRAY)); + + newline(); + } + + /** + * Add information about flags. + */ + public void appendFlags() { + builder.append(TextComponent.of("Flags: ", TextColor.BLUE)); + + appendFlagsList(true); + + newline(); + } + + /** + * Append just the list of flags (without "Flags:"), including colors. + * + * @param useColors true to use colors + */ + public void appendFlagsList(boolean useColors) { + boolean hasFlags = false; + + for (Flag flag : WorldGuard.getInstance().getFlagRegistry()) { + Object val = region.getFlag(flag); + + // No value + if (val == null) { + continue; + } + + if (hasFlags) { + builder.append(TextComponent.of(", ")); + } + + RegionGroupFlag groupFlag = flag.getRegionGroupFlag(); + Object group = null; + if (groupFlag != null) { + group = region.getFlag(groupFlag); + } + + String flagString; + + if (group == null) { + flagString = flag.getName() + ": "; + } else { + flagString = flag.getName() + " -g " + group + ": "; + } + + TextColor flagColor = TextColor.WHITE; + if (useColors) { + // passthrough is ok on global + if (FlagHelperBox.DANGER_ZONE.contains(flag) + && !(region.getId().equals(ProtectedRegion.GLOBAL_REGION) && flag == Flags.PASSTHROUGH)) { + flagColor = TextColor.DARK_RED; + } else if (Flags.INBUILT_FLAGS.contains(flag.getName())) { + flagColor = TextColor.GOLD; + } else if (flag instanceof UnknownFlag) { + flagColor = TextColor.GRAY; + } else { + flagColor = TextColor.LIGHT_PURPLE; + } + } + TextComponent flagText = TextComponent.of(flagString, flagColor) + .append(TextComponent.of(String.valueOf(val), useColors ? TextColor.YELLOW : TextColor.WHITE)); + if (perms != null && perms.maySetFlag(region, flag)) { + flagText = flagText.hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to set flag"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + "/rg flag -w \"" + world + "\" " + region.getId() + " " + flag.getName() + " ")); + } + builder.append(flagText); + + hasFlags = true; + } + + if (!hasFlags) { + TextComponent noFlags = TextComponent.of("(none)", useColors ? TextColor.RED : TextColor.WHITE); + builder.append(noFlags); + } + + if (perms != null && perms.maySetFlag(region)) { + builder.append(TextComponent.space()) + .append(TextComponent.of("[Flags]", useColors ? TextColor.GREEN : TextColor.GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to set a flag"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg flags -w \"" + world + "\" " + region.getId()))); + } + } + + /** + * Add information about parents. + */ + public void appendParents() { + appendParentTree(true); + } + + /** + * Add information about parents. + * + * @param useColors true to use colors + */ + public void appendParentTree(boolean useColors) { + if (region.getParent() == null) { + return; + } + + List inheritance = new ArrayList<>(); + + ProtectedRegion r = region; + inheritance.add(r); + while (r.getParent() != null) { + r = r.getParent(); + inheritance.add(r); + } + + ListIterator it = inheritance.listIterator( + inheritance.size()); + + ProtectedRegion last = null; + int indent = 0; + while (it.hasPrevious()) { + ProtectedRegion cur = it.previous(); + + StringBuilder namePrefix = new StringBuilder(); + + // Put symbol for child + if (indent != 0) { + for (int i = 0; i < indent; i++) { + namePrefix.append(" "); + } + namePrefix.append("\u2937"); //⤷ + } + + // Put name + builder.append(TextComponent.of(namePrefix.toString(), useColors ? TextColor.GREEN : TextColor.WHITE)); + if (perms != null && perms.mayLookup(cur)) { + builder.append(TextComponent.of(cur.getId(), useColors ? TextColor.GREEN : TextColor.WHITE) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click for info"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg info -w \"" + world + "\" " + cur.getId()))); + } else { + builder.append(TextComponent.of(cur.getId(), useColors ? TextColor.GREEN : TextColor.WHITE)); + } + + // Put (parent) + if (!cur.equals(region)) { + builder.append(TextComponent.of(" (parent, priority=", useColors ? TextColor.GRAY : TextColor.WHITE)); + appendPriorityComponent(cur); + builder.append(TextComponent.of(")", useColors ? TextColor.GRAY : TextColor.WHITE)); + } + if (last != null && cur.equals(region) && perms != null && perms.maySetParent(cur, last)) { + builder.append(TextComponent.space()); + builder.append(TextComponent.of("[X]", TextColor.RED) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to unlink parent"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, "/rg setparent -w \"" + world + "\" " + cur.getId()))); + } + + last = cur; + indent++; + newline(); + } + } + + /** + * Add information about members. + */ + public void appendDomain() { + builder.append(TextComponent.of("Owners: ", TextColor.BLUE)); + addDomainString(region.getOwners(), + perms != null && perms.mayAddOwners(region) ? "addowner" : null, + perms != null && perms.mayRemoveOwners(region) ? "removeowner" : null); + newline(); + + builder.append(TextComponent.of("Members: ", TextColor.BLUE)); + addDomainString(region.getMembers(), + perms != null && perms.mayAddMembers(region) ? "addmember" : null, + perms != null && perms.mayRemoveMembers(region) ? "removemember" : null); + newline(); + } + + private void addDomainString(DefaultDomain domain, String addCommand, String removeCommand) { + if (domain.size() == 0) { + builder.append(ErrorFormat.wrap("(none)")); + } else { + if (perms != null) { + builder.append(domain.toUserFriendlyComponent(cache)); + } else { + builder.append(LabelFormat.wrap(domain.toUserFriendlyString(cache))); + } + } + if (addCommand != null) { + builder.append(TextComponent.space().append(TextComponent.of("[Add]", TextColor.GREEN) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to add a player or group"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + "/rg " + addCommand + " -w \"" + world + "\" " + region.getId() + " ")))); + } + if (removeCommand != null && domain.size() > 0) { + builder.append(TextComponent.space().append(TextComponent.of("[Rem]", TextColor.RED) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to remove a player or group"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + "/rg " + removeCommand + " -w \"" + world + "\" " + region.getId() + " ")))); + builder.append(TextComponent.space().append(TextComponent.of("[Clr]", TextColor.RED) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to clear"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, + "/rg " + removeCommand + " -w \"" + world + "\" -a " + region.getId())))); + } + } + + /** + * Add information about coordinates. + */ + public void appendBounds() { + BlockVector3 min = region.getMinimumPoint(); + BlockVector3 max = region.getMaximumPoint(); + builder.append(TextComponent.of("Bounds:", TextColor.BLUE)); + TextComponent bound = TextComponent.of(" " + min + " -> " + max, TextColor.YELLOW); + if (perms != null && perms.maySelect(region)) { + bound = bound + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to select"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, "/rg select " + region.getId())); + } + builder.append(bound); + final Location teleFlag = FlagValueCalculator.getEffectiveFlagOf(region, Flags.TELE_LOC, perms != null && perms.getSender() instanceof RegionAssociable ? (RegionAssociable) perms.getSender() : null); + if (teleFlag != null && perms != null && perms.mayTeleportTo(region)) { + builder.append(TextComponent.space().append(TextComponent.of("[Teleport]", TextColor.GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, + TextComponent.of("Click to teleport").append(TextComponent.newline()).append( + TextComponent.of(teleFlag.getBlockX() + ", " + + teleFlag.getBlockY() + ", " + + teleFlag.getBlockZ())))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, + "/rg tp -w \"" + world + "\" " + region.getId())))); + } + + newline(); + } + + private void appendPriorityComponent(ProtectedRegion rg) { + final String content = String.valueOf(rg.getPriority()); + if (perms != null && perms.maySetPriority(rg)) { + builder.append(TextComponent.of(content, TextColor.GOLD) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to change"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, "/rg setpriority -w \"" + world + "\" " + rg.getId() + " "))); + } else { + builder.append(TextComponent.of(content, TextColor.WHITE)); + } + } + + private void appendRegionInformation() { + appendBasics(); + appendFlags(); + appendParents(); + appendDomain(); + appendBounds(); + + if (cache != null && perms == null) { + builder.append(SubtleFormat.wrap("Any names suffixed by * are 'last seen names' and may not be up to date.")); + } + } + + @Override + public TextComponent call() { + MessageBox box = new MessageBox("Region Info", builder); + appendRegionInformation(); + return box.create(); + } + + /** + * Send the report to a {@link Actor}. + * + * @param sender the recipient + */ + public void send(Actor sender) { + sender.print(toComponent()); + } + + public TextComponentProducer append(String str) { + return builder.append(TextComponent.of(str)); + } + + public TextComponentProducer append(TextComponent component) { + return builder.append(component); + } + + public TextComponent toComponent() { + return builder.create(); + } + + @Override + public String toString() { + return builder.toString().trim(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionAdder.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionAdder.java new file mode 100644 index 000000000..932b85827 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionAdder.java @@ -0,0 +1,104 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.task; + +import com.sk89q.minecraft.util.commands.CommandContext; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.DomainInputResolver; +import com.sk89q.worldguard.protection.util.DomainInputResolver.UserLocatorPolicy; + +import javax.annotation.Nullable; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Creates a new region. + */ +public class RegionAdder implements Callable { + + private final RegionManager manager; + private final ProtectedRegion region; + @Nullable + private String[] ownersInput; + private UserLocatorPolicy locatorPolicy = UserLocatorPolicy.UUID_ONLY; + + /** + * Create a new instance. + * + * @param manager the manage + * @param region the region + */ + public RegionAdder(RegionManager manager, ProtectedRegion region) { + checkNotNull(manager); + checkNotNull(region); + + this.manager = manager; + this.region = region; + } + + /** + * Add the owners from the command's arguments. + * + * @param args the arguments + * @param namesIndex the index in the list of arguments to read the first name from + */ + public void addOwnersFromCommand(CommandContext args, int namesIndex) { + if (args.argsLength() >= namesIndex) { + setLocatorPolicy(args.hasFlag('n') ? UserLocatorPolicy.NAME_ONLY : UserLocatorPolicy.UUID_ONLY); + setOwnersInput(args.getSlice(namesIndex)); + } + } + + @Override + public ProtectedRegion call() throws Exception { + if (ownersInput != null) { + DomainInputResolver resolver = new DomainInputResolver(WorldGuard.getInstance().getProfileService(), ownersInput); + resolver.setLocatorPolicy(locatorPolicy); + DefaultDomain domain = resolver.call(); + region.getOwners().addAll(domain); + } + + manager.addRegion(region); + + return region; + } + + @Nullable + public String[] getOwnersInput() { + return ownersInput; + } + + public void setOwnersInput(@Nullable String[] ownersInput) { + this.ownersInput = ownersInput; + } + + public UserLocatorPolicy getLocatorPolicy() { + return locatorPolicy; + } + + public void setLocatorPolicy(UserLocatorPolicy locatorPolicy) { + this.locatorPolicy = locatorPolicy; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionLister.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionLister.java new file mode 100644 index 000000000..096c682e3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionLister.java @@ -0,0 +1,282 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.task; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldguard.util.profile.Profile; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.component.PaginationBox; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class RegionLister implements Callable { + + private static final Logger log = Logger.getLogger(RegionLister.class.getCanonicalName()); + + private final Actor sender; + private final RegionManager manager; + private final String world; + private OwnerMatcher ownerMatcher; + private int page; + private String playerName; + private boolean nameOnly; + + public RegionLister(RegionManager manager, Actor sender, String world) { + checkNotNull(manager); + checkNotNull(sender); + checkNotNull(world); + + this.manager = manager; + this.sender = sender; + this.world = world; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public void filterOwnedByName(String name, boolean nameOnly) { + this.playerName = name; + this.nameOnly = nameOnly; + if (nameOnly) { + filterOwnedByName(name); + } else { + filterOwnedByProfile(name); + } + } + + private void filterOwnedByName(final String name) { + ownerMatcher = new OwnerMatcher() { + @Override + public String getName() { + return name; + } + + @Override + public boolean isContainedWithin(DefaultDomain domain) { + return domain.contains(name); + } + }; + } + + private void filterOwnedByProfile(final String name) { + ownerMatcher = new OwnerMatcher() { + private UUID uniqueId; + + @Override + public String getName() { + return name; + } + + @Override + public boolean isContainedWithin(DefaultDomain domain) throws CommandException { + if (domain.contains(name)) { + return true; + } + + if (uniqueId == null) { + Profile profile; + + try { + profile = WorldGuard.getInstance().getProfileService().findByName(name); + } catch (IOException e) { + log.log(Level.WARNING, "Failed UUID lookup of '" + name + "'", e); + throw new CommandException("Failed to lookup the UUID of '" + name + "'"); + } catch (InterruptedException e) { + log.log(Level.WARNING, "Failed UUID lookup of '" + name + "'", e); + throw new CommandException("The lookup the UUID of '" + name + "' was interrupted"); + } + + if (profile == null) { + throw new CommandException("A user by the name of '" + name + "' does not seem to exist."); + } + + uniqueId = profile.getUniqueId(); + } + + return domain.contains(uniqueId); + } + }; + } + + @Override + public Integer call() throws Exception { + Map regions = manager.getRegions(); + + // Build a list of regions to show + List entries = new ArrayList<>(); + + for (Map.Entry rg : regions.entrySet()) { + if (rg.getKey().equals("__global__")) { + continue; + } + final ProtectedRegion region = rg.getValue(); + final RegionListEntry entry = new RegionListEntry(region); + if (entry.matches(ownerMatcher)) { + entries.add(entry); + } + } + + if (ownerMatcher == null) { + Collections.sort(entries); + } + // insert global on top + if (regions.containsKey("__global__")) { + final RegionListEntry entry = new RegionListEntry(regions.get("__global__")); + if (entry.matches(ownerMatcher)) { + entries.add(0, entry); + } + } + // unless we're matching owners, then sort by ownership + if (ownerMatcher != null) { + Collections.sort(entries); + } + + RegionPermissionModel perms = sender.isPlayer() ? new RegionPermissionModel(sender) : null; + String title = ownerMatcher == null ? "Regions" : "Regions for " + ownerMatcher.getName(); + String cmd = "/rg list -w \"" + world + "\"" + + (playerName != null ? " -p " + playerName : "") + + (nameOnly ? " -n" : "") + + " %page%"; + PaginationBox box = new RegionListBox(title, cmd, perms, entries, world); + sender.print(box.create(page)); + + return page; + } + + private interface OwnerMatcher { + String getName(); + + boolean isContainedWithin(DefaultDomain domain) throws CommandException; + } + + private static final class RegionListEntry implements Comparable { + private final ProtectedRegion region; + private boolean isOwner; + private boolean isMember; + + private RegionListEntry(ProtectedRegion rg) { + this.region = rg; + } + + public boolean matches(OwnerMatcher matcher) throws CommandException { + return matcher == null + || (isOwner = matcher.isContainedWithin(region.getOwners())) + || (isMember = matcher.isContainedWithin(region.getMembers())); + } + + public ProtectedRegion getRegion() { + return region; + } + + public boolean isOwner() { + return isOwner; + } + + public boolean isMember() { + return isMember; + } + + @Override + public int compareTo(RegionListEntry o) { + if (isOwner != o.isOwner) { + return isOwner ? -1 : 1; + } + if (isMember != o.isMember) { + return isMember ? -1 : 1; + } + return region.getId().compareTo(o.region.getId()); + } + } + + private static class RegionListBox extends PaginationBox { + private final RegionPermissionModel perms; + private final List entries; + private String world; + + RegionListBox(String title, String cmd, RegionPermissionModel perms, List entries, String world) { + super(title, cmd); + this.perms = perms; + this.entries = entries; + this.world = world; + } + + @Override + public Component getComponent(int number) { + final RegionListEntry entry = entries.get(number); + final TextComponent.Builder builder = TextComponent.builder(number + 1 + ".").color(TextColor.LIGHT_PURPLE); + if (entry.isOwner()) { + builder.append(TextComponent.space()).append(TextComponent.of("+", TextColor.DARK_AQUA) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Region Owner", TextColor.GOLD)))); + } else if (entry.isMember()) { + builder.append(TextComponent.space()).append(TextComponent.of("-", TextColor.AQUA) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Region Member", TextColor.GOLD)))); + } + builder.append(TextComponent.space()).append(TextComponent.of(entry.getRegion().getId(), TextColor.GOLD)); + if (perms != null && perms.mayLookup(entry.region)) { + builder.append(TextComponent.space().append(TextComponent.of("[Info]", TextColor.GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click for info"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, + "/rg info -w \"" + world + "\" " + entry.region.getId())))); + } + final Location teleFlag = FlagValueCalculator.getEffectiveFlagOf(entry.region, Flags.TELE_LOC, perms != null && perms.getSender() instanceof RegionAssociable ? (RegionAssociable) perms.getSender() : null); + if (perms != null && teleFlag != null && perms.mayTeleportTo(entry.region)) { + builder.append(TextComponent.space().append(TextComponent.of("[TP]", TextColor.GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to teleport"))) + .clickEvent(ClickEvent.of(ClickEvent.Action.RUN_COMMAND, + "/rg tp -w \"" + world + "\" " + entry.region.getId())))); + } + return builder.build(); + } + + @Override + public int getComponentsSize() { + return entries.size(); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerLoader.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerLoader.java new file mode 100644 index 000000000..0343cdcd1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerLoader.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.task; + +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.StorageException; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class RegionManagerLoader implements Callable> { + + private final Collection managers; + + public RegionManagerLoader(Collection managers) { + checkNotNull(managers); + this.managers = managers; + } + + public RegionManagerLoader(RegionManager... manager) { + this(Arrays.asList(manager)); + } + + @Override + public Collection call() throws StorageException { + for (RegionManager manager : managers) { + manager.load(); + } + + return managers; + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerSaver.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerSaver.java new file mode 100644 index 000000000..e23c17a41 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionManagerSaver.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.task; + +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.StorageException; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class RegionManagerSaver implements Callable> { + + private final Collection managers; + + public RegionManagerSaver(Collection managers) { + checkNotNull(managers); + this.managers = managers; + } + + public RegionManagerSaver(RegionManager... manager) { + this(Arrays.asList(manager)); + } + + @Override + public Collection call() throws StorageException { + for (RegionManager manager : managers) { + manager.save(); + } + + return managers; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionRemover.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionRemover.java new file mode 100644 index 000000000..1f9fab554 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/commands/task/RegionRemover.java @@ -0,0 +1,93 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.commands.task; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Set; +import java.util.concurrent.Callable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Removes a region. + */ +public class RegionRemover implements Callable> { + + private final RegionManager manager; + private final ProtectedRegion region; + @Nullable + private RemovalStrategy removalStrategy; + + /** + * Create a new instance. + * + * @param manager a region manager + * @param region the region to remove + */ + public RegionRemover(RegionManager manager, ProtectedRegion region) { + checkNotNull(manager); + checkNotNull(region); + this.manager = manager; + this.region = region; + } + + /** + * GSet a parent removal strategy. + * + * @return a removal strategy or null (see{@link #setRemovalStrategy(RemovalStrategy)} + */ + @Nullable + public RemovalStrategy getRemovalStrategy() { + return removalStrategy; + } + + /** + * Set a parent removal strategy. Set it to {@code null} to have the code + * check for children and throw an error if any are found. + * + * @param removalStrategy a removal strategy, or {@code null} to error if children exist + */ + public void setRemovalStrategy(@Nullable RemovalStrategy removalStrategy) { + this.removalStrategy = removalStrategy; + } + + @Override + public Set call() throws Exception { + if (removalStrategy == null) { + for (ProtectedRegion test : manager.getRegions().values()) { + ProtectedRegion parent = test.getParent(); + if (parent != null && parent.equals(region)) { + throw new CommandException( + "The region '" + region.getId() + "' has child regions. Use -f to force removal of children " + + "or -u to unset the parent value of these children."); + } + } + + return manager.removeRegion(region.getId(), RemovalStrategy.UNSET_PARENT_IN_CHILDREN); + } else { + return manager.removeRegion(region.getId(), removalStrategy); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java new file mode 100644 index 000000000..5f159df49 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/ConfigurationManager.java @@ -0,0 +1,173 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.config; + +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.managers.storage.DriverType; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.session.handler.WaterBreathing; +import com.sk89q.worldedit.util.report.Unreported; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** + * Represents the global configuration and also delegates configuration + * for individual worlds. + * + * @author sk89q + * @author Michael + */ +public abstract class ConfigurationManager { + + protected static final Logger log = Logger.getLogger(ConfigurationManager.class.getCanonicalName()); + + static final String CONFIG_HEADER = "#\r\n" + + "# WorldGuard's main configuration file\r\n" + + "#\r\n" + + "# This is the global configuration file. Anything placed into here will\r\n" + + "# be applied to all worlds. However, each world has its own configuration\r\n" + + "# file to allow you to replace most settings in here for that world only.\r\n" + + "#\r\n" + + "# About editing this file:\r\n" + + "# - DO NOT USE TABS. You MUST use spaces or Bukkit will complain. If\r\n" + + "# you use an editor like Notepad++ (recommended for Windows users), you\r\n" + + "# must configure it to \"replace tabs with spaces.\" In Notepad++, this can\r\n" + + "# be changed in Settings > Preferences > Language Menu.\r\n" + + "# - Don't get rid of the indents. They are indented so some entries are\r\n" + + "# in categories (like \"enforce-single-session\" is in the \"protection\"\r\n" + + "# category.\r\n" + + "# - If you want to check the format of this file before putting it\r\n" + + "# into WorldGuard, paste it into http://yaml-online-parser.appspot.com/\r\n" + + "# and see if it gives \"ERROR:\".\r\n" + + "# - Lines starting with # are comments and so they are ignored.\r\n" + + "#\r\n"; + + public boolean useRegionsCreatureSpawnEvent; + public boolean activityHaltToggle = false; + public boolean useGodPermission; + public boolean useGodGroup; + public boolean useAmphibiousGroup; + public boolean usePlayerMove; + public boolean usePlayerTeleports; + public boolean deopOnJoin; + public boolean blockInGameOp; + public boolean migrateRegionsToUuid; + public boolean keepUnresolvedNames; + public boolean particleEffects; + public boolean disablePermissionCache; + public boolean disableDefaultBypass; + public boolean announceBypassStatus; + + @Unreported public Map hostKeys = new HashMap<>(); + public boolean hostKeysAllowFMLClients; + + /** + * Region Storage Configuration method, and config values + */ + @Unreported public RegionDriver selectedRegionStoreDriver; + @Unreported public Map regionStoreDriverMap; + + /** + * Get the folder for storing data files and configuration. + * + * @return the data folder + */ + public abstract File getDataFolder(); + + /** + * Get the folder for storing data files and configuration for each + * world. + * + * @return the data folder + */ + public File getWorldsDataFolder() { + return new File(getDataFolder(), "worlds"); + } + + /** + * Load the configuration. + */ + public abstract void load(); + + /** + * Unload the configuration. + */ + public abstract void unload(); + + /** + * Get the configuration for a world. + * + * @param world The world to get the configuration for + * @return {@code world}'s configuration + */ + public abstract WorldConfiguration get(World world); + + public abstract void disableUuidMigration(); + + /** + * Check to see if god mode is enabled for a player. + * + * @param player The player to check + * @return Whether the player has godmode through WorldGuard or CommandBook + */ + public boolean hasGodMode(LocalPlayer player) { + return WorldGuard.getInstance().getPlatform().getSessionManager().get(player).isInvincible(player); + } + + /** + * Enable amphibious mode for a player. + * + * @param player The player to enable amphibious mode for + */ + public void enableAmphibiousMode(LocalPlayer player) { + WaterBreathing handler = WorldGuard.getInstance().getPlatform().getSessionManager().get(player).getHandler(WaterBreathing.class); + if (handler != null) { + handler.setWaterBreathing(true); + } + } + + /** + * Disable amphibious mode for a player. + * + * @param player The player to disable amphibious mode for + */ + public void disableAmphibiousMode(LocalPlayer player) { + WaterBreathing handler = WorldGuard.getInstance().getPlatform().getSessionManager().get(player).getHandler(WaterBreathing.class); + if (handler != null) { + handler.setWaterBreathing(false); + } + } + + /** + * Check to see if amphibious mode is enabled for a player. + * + * @param player The player to check + * @return Whether {@code player} has amphibious mode + */ + public boolean hasAmphibiousMode(LocalPlayer player) { + WaterBreathing handler = WorldGuard.getInstance().getPlatform().getSessionManager().get(player).getHandler(WaterBreathing.class); + return handler != null && handler.hasWaterBreathing(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/WorldConfiguration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/WorldConfiguration.java new file mode 100644 index 000000000..b6fbe2cee --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/WorldConfiguration.java @@ -0,0 +1,264 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.config; + +import com.sk89q.worldedit.util.report.Unreported; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.item.ItemTypes; +import com.sk89q.worldedit.world.registry.LegacyMapper; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.blacklist.Blacklist; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Holds the configuration for individual worlds. + * + * @author sk89q + * @author Michael + */ +public abstract class WorldConfiguration { + + public static final Logger log = Logger.getLogger(WorldConfiguration.class.getCanonicalName()); + + public static final String CONFIG_HEADER = "#\r\n" + + "# WorldGuard's world configuration file\r\n" + + "#\r\n" + + "# This is a world configuration file. Anything placed into here will only\r\n" + + "# affect this world. If you don't put anything in this file, then the\r\n" + + "# settings will be inherited from the main configuration file.\r\n" + + "#\r\n" + + "# If you see {} below, that means that there are NO entries in this file.\r\n" + + "# Remove the {} and add your own entries.\r\n" + + "#\r\n"; + + protected File blacklistFile; + + @Unreported + protected Blacklist blacklist; + + public boolean boundedLocationFlags; + public boolean useRegions; + public boolean simulateSponge; + public int spongeRadius; + public boolean redstoneSponges; + public boolean summaryOnStart; + public boolean opPermissions; + public boolean buildPermissions; + public String buildPermissionDenyMessage = ""; + public boolean fireSpreadDisableToggle; + public boolean itemDurability; + public boolean disableExpDrops; + public boolean blockPotionsAlways; + public boolean disableConduitEffects; + public boolean pumpkinScuba; + public boolean noPhysicsGravel; + public boolean noPhysicsSand; + public boolean ropeLadders; + public boolean allowPortalAnywhere; + public Set preventWaterDamage; + public boolean blockLighter; + public boolean disableFireSpread; + public Set disableFireSpreadBlocks; + public boolean preventLavaFire; + public Set allowedLavaSpreadOver; + public boolean blockTNTExplosions; + public boolean blockTNTBlockDamage; + public boolean blockCreeperExplosions; + public boolean blockCreeperBlockDamage; + public boolean blockWitherExplosions; + public boolean blockWitherBlockDamage; + public boolean blockWitherSkullExplosions; + public boolean blockWitherSkullBlockDamage; + public boolean blockEnderDragonBlockDamage; + public boolean blockEnderDragonPortalCreation; + public boolean blockFireballExplosions; + public boolean blockFireballBlockDamage; + public boolean blockOtherExplosions; + public boolean blockEntityPaintingDestroy; + public boolean blockEntityItemFrameDestroy; + public boolean blockEntityArmorStandDestroy; + public boolean blockEntityVehicleEntry; + public boolean blockPluginSpawning; + public boolean blockGroundSlimes; + public boolean blockZombieDoorDestruction; + public boolean disableContactDamage; + public boolean disableFallDamage; + public boolean disableLavaDamage; + public boolean disableFireDamage; + public boolean disableLightningDamage; + public boolean disableDrowningDamage; + public boolean disableSuffocationDamage; + public boolean teleportOnSuffocation; + public boolean disableVoidDamage; + public boolean teleportOnVoid; + public boolean safeFallOnVoid; + public boolean disableExplosionDamage; + public boolean disableMobDamage; + public boolean highFreqFlags; + public boolean checkLiquidFlow; + public String regionWand; + public Set blockCreatureSpawn; + public boolean allowTamedSpawns; + public int maxClaimVolume; + public boolean claimOnlyInsideExistingRegions; + public int maxRegionCountPerPlayer; + public boolean antiWolfDumbness; + public boolean signChestProtection; + public boolean disableSignChestProtectionCheck; + public boolean removeInfiniteStacks; + public boolean disableCreatureCropTrampling; + public boolean disablePlayerCropTrampling; + public boolean disableCreatureTurtleEggTrampling; + public boolean disablePlayerTurtleEggTrampling; + public boolean preventLightningFire; + public Set disallowedLightningBlocks; + public boolean disableThunder; + public boolean disableWeather; + public boolean alwaysRaining; + public boolean alwaysThundering; + public boolean disablePigZap; + public boolean disableVillagerZap; + public boolean disableCreeperPower; + public boolean disableHealthRegain; + public boolean disableMushroomSpread; + public boolean disableIceMelting; + public boolean disableSnowMelting; + public boolean disableSnowFormation; + public boolean disableIceFormation; + public boolean disableLeafDecay; + public boolean disableGrassGrowth; + public boolean disableMyceliumSpread; + public boolean disableVineGrowth; + public boolean disableCropGrowth; + public boolean disableEndermanGriefing; + public boolean disableSnowmanTrails; + public boolean disableSoilDehydration; + public boolean disableCoralBlockFade; + public Set allowedSnowFallOver; + public boolean regionInvinciblityRemovesMobs; + public boolean regionCancelEmptyChatEvents; + public boolean regionNetherPortalProtection; + public boolean forceDefaultTitleTimes; + public boolean fakePlayerBuildOverride; + public boolean explosionFlagCancellation; + public boolean disableDeathMessages; + public boolean disableObsidianGenerators; + public boolean strictEntitySpawn; + public boolean ignoreHopperMoveEvents; + public boolean breakDeniedHoppers; + public boolean useMaxPriorityAssociation; + protected Map maxRegionCounts; + + /** + * Load the configuration. + */ + public abstract void loadConfiguration(); + + public Blacklist getBlacklist() { + return this.blacklist; + } + + public List convertLegacyItems(List legacyItems) { + return legacyItems.stream().map(this::convertLegacyItem).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public String convertLegacyItem(String legacy) { + String[] splitter = legacy.split(":", 2); + try { + int id; + byte data; + if (splitter.length == 1) { + id = Integer.parseInt(splitter[0]); + data = 0; + } else { + id = Integer.parseInt(splitter[0]); + data = Byte.parseByte(splitter[1]); + } + ItemType legacyItem = LegacyMapper.getInstance().getItemFromLegacy(id, data); + if (legacyItem != null) { + return legacyItem.getId(); + } + } catch (NumberFormatException ignored) { + } + final ItemType itemType = ItemTypes.get(legacy); + if (itemType != null) { + return itemType.getId(); + } + + return null; + } + + public List convertLegacyBlocks(List legacyBlocks) { + return legacyBlocks.stream().map(this::convertLegacyBlock).filter(Objects::nonNull).collect(Collectors.toList()); + } + + public String convertLegacyBlock(String legacy) { + String[] splitter = legacy.split(":", 2); + try { + int id; + byte data; + if (splitter.length == 1) { + id = Integer.parseInt(splitter[0]); + data = 0; + } else { + id = Integer.parseInt(splitter[0]); + data = Byte.parseByte(splitter[1]); + } + BlockState legacyBlock = LegacyMapper.getInstance().getBlockFromLegacy(id, data); + if (legacyBlock != null) { + return legacyBlock.getBlockType().getId(); + } + } catch (NumberFormatException ignored) { + } + final BlockType blockType = BlockTypes.get(legacy); + if (blockType != null) { + return blockType.getId(); + } + + return null; + } + + public int getMaxRegionCount(LocalPlayer player) { + int max = -1; + for (String group : player.getGroups()) { + if (maxRegionCounts.containsKey(group)) { + int groupMax = maxRegionCounts.get(group); + if (max < groupMax) { + max = groupMax; + } + } + } + if (max <= -1) { + max = maxRegionCountPerPlayer; + } + return max; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java new file mode 100644 index 000000000..801fcf381 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlConfigurationManager.java @@ -0,0 +1,125 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.config; + +import com.google.common.collect.ImmutableMap; +import com.sk89q.util.yaml.YAMLFormat; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldguard.protection.managers.storage.DriverType; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.file.DirectoryYamlDriver; +import com.sk89q.worldguard.protection.managers.storage.sql.SQLDriver; +import com.sk89q.worldedit.util.report.Unreported; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public abstract class YamlConfigurationManager extends ConfigurationManager { + + @Unreported private YAMLProcessor config; + + public abstract void copyDefaults(); + + @Override + public void load() { + copyDefaults(); + + config = new YAMLProcessor(new File(getDataFolder(), "config.yml"), true, YAMLFormat.EXTENDED); + try { + config.load(); + } catch (IOException e) { + log.severe("Error reading configuration for global config: "); + e.printStackTrace(); + } + + config.removeProperty("suppress-tick-sync-warnings"); + migrateRegionsToUuid = config.getBoolean("regions.uuid-migration.perform-on-next-start", true); + keepUnresolvedNames = config.getBoolean("regions.uuid-migration.keep-names-that-lack-uuids", true); + useRegionsCreatureSpawnEvent = config.getBoolean("regions.use-creature-spawn-event", true); + disableDefaultBypass = config.getBoolean("regions.disable-bypass-by-default", false); + announceBypassStatus = config.getBoolean("regions.announce-bypass-status", false); + + useGodPermission = config.getBoolean("auto-invincible", config.getBoolean("auto-invincible-permission", false)); + useGodGroup = config.getBoolean("auto-invincible-group", false); + useAmphibiousGroup = config.getBoolean("auto-no-drowning-group", false); + config.removeProperty("auto-invincible-permission"); + usePlayerMove = config.getBoolean("use-player-move-event", true); + usePlayerTeleports = config.getBoolean("use-player-teleports", true); + particleEffects = config.getBoolean("use-particle-effects", true); + disablePermissionCache = config.getBoolean("disable-permission-cache", false); + + deopOnJoin = config.getBoolean("security.deop-everyone-on-join", false); + blockInGameOp = config.getBoolean("security.block-in-game-op-command", false); + + hostKeys = new HashMap<>(); + Object hostKeysRaw = config.getProperty("host-keys"); + if (!(hostKeysRaw instanceof Map)) { + config.setProperty("host-keys", new HashMap()); + } else { + for (Map.Entry entry : ((Map) hostKeysRaw).entrySet()) { + String key = String.valueOf(entry.getKey()); + String value = String.valueOf(entry.getValue()); + hostKeys.put(key.toLowerCase(), value); + } + } + hostKeysAllowFMLClients = config.getBoolean("security.host-keys-allow-forge-clients", false); + + // ==================================================================== + // Region store drivers + // ==================================================================== + + boolean useSqlDatabase = config.getBoolean("regions.sql.use", false); + String sqlDsn = config.getString("regions.sql.dsn", "jdbc:mysql://localhost/worldguard"); + String sqlUsername = config.getString("regions.sql.username", "worldguard"); + String sqlPassword = config.getString("regions.sql.password", "worldguard"); + String sqlTablePrefix = config.getString("regions.sql.table-prefix", ""); + + DataSourceConfig dataSourceConfig = new DataSourceConfig(sqlDsn, sqlUsername, sqlPassword, sqlTablePrefix); + SQLDriver sqlDriver = new SQLDriver(dataSourceConfig); + DirectoryYamlDriver yamlDriver = new DirectoryYamlDriver(getWorldsDataFolder(), "regions.yml"); + + this.regionStoreDriverMap = ImmutableMap.builder() + .put(DriverType.MYSQL, sqlDriver) + .put(DriverType.YAML, yamlDriver) + .build(); + this.selectedRegionStoreDriver = useSqlDatabase ? sqlDriver : yamlDriver; + + postLoad(); + + config.setHeader(CONFIG_HEADER); + } + + public void postLoad() {} + + public YAMLProcessor getConfig() { + return config; + } + + @Override + public void disableUuidMigration() { + config.setProperty("regions.uuid-migration.perform-on-next-start", false); + if (!config.save()) { + log.severe("Error saving configuration!"); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlWorldConfiguration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlWorldConfiguration.java new file mode 100644 index 000000000..86dfc6ed0 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/config/YamlWorldConfiguration.java @@ -0,0 +1,125 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.config; + +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.util.report.Unreported; + +import java.util.ArrayList; +import java.util.List; + +public abstract class YamlWorldConfiguration extends WorldConfiguration { + + @Unreported protected YAMLProcessor parentConfig; + @Unreported protected YAMLProcessor config; + + public boolean getBoolean(String node, boolean def) { + boolean val = parentConfig.getBoolean(node, def); + + if (config.getProperty(node) != null) { + return config.getBoolean(node, def); + } else { + return val; + } + } + + public String getString(String node, String def) { + String val = parentConfig.getString(node, def); + + if (config.getProperty(node) != null) { + return config.getString(node, def); + } else { + return val; + } + } + + public int getInt(String node, int def) { + int val = parentConfig.getInt(node, def); + + if (config.getProperty(node) != null) { + return config.getInt(node, def); + } else { + return val; + } + } + + @SuppressWarnings("unused") + private double getDouble(String node, double def) { + double val = parentConfig.getDouble(node, def); + + if (config.getProperty(node) != null) { + return config.getDouble(node, def); + } else { + return val; + } + } + + public List getIntList(String node, List def) { + List res = parentConfig.getIntList(node, def); + + if (res == null || res.size() == 0) { + parentConfig.setProperty(node, new ArrayList()); + } + + if (config.getProperty(node) != null) { + res = config.getIntList(node, def); + } + + return res; + } + + public List getStringList(String node, List def) { + List res = parentConfig.getStringList(node, def); + + if (res == null || res.size() == 0) { + parentConfig.setProperty(node, new ArrayList()); + } + + if (config.getProperty(node) != null) { + res = config.getStringList(node, def); + } + + return res; + } + + public List getKeys(String node) { + List res = parentConfig.getKeys(node); + + if (res == null || res.size() == 0) { + res = config.getKeys(node); + } + if (res == null) { + res = new ArrayList<>(); + } + + return res; + } + + public Object getProperty(String node) { + Object res = parentConfig.getProperty(node); + + if (config.getProperty(node) != null) { + res = config.getProperty(node); + } + + return res; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Association.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Association.java new file mode 100644 index 000000000..d15b60e38 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Association.java @@ -0,0 +1,31 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +/** + * Indicates the level of membership. + */ +public enum Association { + + OWNER, + MEMBER, + NON_MEMBER + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java new file mode 100644 index 000000000..b7118cb6c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/DefaultDomain.java @@ -0,0 +1,458 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.sk89q.worldguard.util.profile.Profile; +import com.sk89q.worldguard.util.profile.cache.ProfileCache; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A combination of a {@link PlayerDomain} and a {@link GroupDomain}. + */ +public class DefaultDomain implements Domain, ChangeTracked { + + private PlayerDomain playerDomain = new PlayerDomain(); + private GroupDomain groupDomain = new GroupDomain(); + + /** + * Create a new domain. + */ + public DefaultDomain() { + } + + /** + * Create a new domain from an existing one, making a copy of all values. + * + * @param existing the other domain to copy values from + */ + public DefaultDomain(DefaultDomain existing) { + setPlayerDomain(existing.getPlayerDomain()); + setGroupDomain(existing.getGroupDomain()); + } + + /** + * Get the domain that holds the players. + * + * @return a domain + */ + public PlayerDomain getPlayerDomain() { + return playerDomain; + } + + /** + * Set a new player domain. + * + * @param playerDomain a domain + */ + public void setPlayerDomain(PlayerDomain playerDomain) { + checkNotNull(playerDomain); + this.playerDomain = new PlayerDomain(playerDomain); + } + + /** + * Set the domain that holds the groups. + * + * @return a domain + */ + public GroupDomain getGroupDomain() { + return groupDomain; + } + + /** + * Set a new group domain. + * + * @param groupDomain a domain + */ + public void setGroupDomain(GroupDomain groupDomain) { + checkNotNull(groupDomain); + this.groupDomain = new GroupDomain(groupDomain); + } + + /** + * Add the given player to the domain, identified by the player's name. + * + * @param name the name of the player + */ + public void addPlayer(String name) { + playerDomain.addPlayer(name); + } + + /** + * Remove the given player from the domain, identified by the player's name. + * + * @param name the name of the player + */ + public void removePlayer(String name) { + playerDomain.removePlayer(name); + } + + /** + * Remove the given player from the domain, identified by the player's UUID. + * + * @param uuid the UUID of the player + */ + public void removePlayer(UUID uuid) { + playerDomain.removePlayer(uuid); + } + + /** + * Add the given player to the domain, identified by the player's UUID. + * + * @param uniqueId the UUID of the player + */ + public void addPlayer(UUID uniqueId) { + playerDomain.addPlayer(uniqueId); + } + + /** + * Remove the given player from the domain, identified by either the + * player's name, the player's unique ID, or both. + * + * @param player the player + */ + public void removePlayer(LocalPlayer player) { + playerDomain.removePlayer(player); + } + + /** + * Add the given player to the domain, identified by the player's UUID. + * + * @param player the player + */ + public void addPlayer(LocalPlayer player) { + playerDomain.addPlayer(player); + } + + /** + * Add all the entries from another domain. + * + * @param other the other domain + */ + public void addAll(DefaultDomain other) { + checkNotNull(other); + for (String player : other.getPlayers()) { + addPlayer(player); + } + for (UUID uuid : other.getUniqueIds()) { + addPlayer(uuid); + } + for (String group : other.getGroups()) { + addGroup(group); + } + } + + /** + * Remove all the entries from another domain. + * + * @param other the other domain + */ + public void removeAll(DefaultDomain other) { + checkNotNull(other); + for (String player : other.getPlayers()) { + removePlayer(player); + } + for (UUID uuid : other.getUniqueIds()) { + removePlayer(uuid); + } + for (String group : other.getGroups()) { + removeGroup(group); + } + } + + /** + * Get the set of player names. + * + * @return the set of player names + */ + public Set getPlayers() { + return playerDomain.getPlayers(); + } + + /** + * Get the set of player UUIDs. + * + * @return the set of player UUIDs + */ + public Set getUniqueIds() { + return playerDomain.getUniqueIds(); + } + + /** + * Add the name of the group to the domain. + * + * @param name the name of the group. + */ + public void addGroup(String name) { + groupDomain.addGroup(name); + } + + /** + * Remove the given group from the domain. + * + * @param name the name of the group + */ + public void removeGroup(String name) { + groupDomain.removeGroup(name); + } + + /** + * Get the set of group names. + * + * @return the set of group names + */ + public Set getGroups() { + return groupDomain.getGroups(); + } + + @Override + public boolean contains(LocalPlayer player) { + return playerDomain.contains(player) || groupDomain.contains(player); + } + + @Override + public boolean contains(UUID uniqueId) { + return playerDomain.contains(uniqueId); + } + + @Override + public boolean contains(String playerName) { + return playerDomain.contains(playerName); + } + + @Override + public int size() { + return groupDomain.size() + playerDomain.size(); + } + + @Override + public void clear() { + playerDomain.clear(); + groupDomain.clear(); + } + + public void removeAll() { + clear(); + } + + public String toPlayersString() { + return toPlayersString(null); + } + + public String toPlayersString(@Nullable ProfileCache cache) { + StringBuilder str = new StringBuilder(); + List output = new ArrayList<>(); + + for (String name : playerDomain.getPlayers()) { + output.add("name:" + name); + } + + if (cache != null) { + ImmutableMap results = cache.getAllPresent(playerDomain.getUniqueIds()); + for (UUID uuid : playerDomain.getUniqueIds()) { + Profile profile = results.get(uuid); + if (profile != null) { + output.add(profile.getName() + "*"); + } else { + output.add("uuid:" + uuid); + } + } + } else { + for (UUID uuid : playerDomain.getUniqueIds()) { + output.add("uuid:" + uuid); + } + } + + output.sort(String.CASE_INSENSITIVE_ORDER); + for (Iterator it = output.iterator(); it.hasNext();) { + str.append(it.next()); + if (it.hasNext()) { + str.append(", "); + } + } + return str.toString(); + } + + public String toGroupsString() { + StringBuilder str = new StringBuilder(); + for (Iterator it = groupDomain.getGroups().iterator(); it.hasNext(); ) { + str.append("g:"); + str.append(it.next()); + if (it.hasNext()) { + str.append(", "); + } + } + return str.toString(); + } + + public String toUserFriendlyString() { + StringBuilder str = new StringBuilder(); + + if (playerDomain.size() > 0) { + str.append(toPlayersString()); + } + + if (groupDomain.size() > 0) { + if (str.length() > 0) { + str.append("; "); + } + + str.append(toGroupsString()); + } + + return str.toString(); + } + + public String toUserFriendlyString(ProfileCache cache) { + StringBuilder str = new StringBuilder(); + + if (playerDomain.size() > 0) { + str.append(toPlayersString(cache)); + } + + if (groupDomain.size() > 0) { + if (str.length() > 0) { + str.append("; "); + } + + str.append(toGroupsString()); + } + + return str.toString(); + } + + public Component toUserFriendlyComponent(@Nullable ProfileCache cache) { + final TextComponent.Builder builder = TextComponent.builder(""); + if (playerDomain.size() > 0) { + builder.append(toPlayersComponent(cache)); + } + if (groupDomain.size() > 0) { + if (playerDomain.size() > 0) { + builder.append(TextComponent.of("; ")); + } + builder.append(toGroupsComponent()); + } + return builder.build(); + } + + private Component toGroupsComponent() { + final TextComponent.Builder builder = TextComponent.builder(""); + for (Iterator it = groupDomain.getGroups().iterator(); it.hasNext(); ) { + builder.append(TextComponent.of("g:", TextColor.GRAY)) + .append(TextComponent.of(it.next(), TextColor.GOLD)); + if (it.hasNext()) { + builder.append(TextComponent.of(", ")); + } + } + return builder.build().hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Groups"))); + } + + private Component toPlayersComponent(ProfileCache cache) { + List uuids = Lists.newArrayList(); + Map profileMap = Maps.newHashMap(); + + for (String name : playerDomain.getPlayers()) { + profileMap.put(name, null); + } + + if (cache != null) { + ImmutableMap results = cache.getAllPresent(playerDomain.getUniqueIds()); + for (UUID uuid : playerDomain.getUniqueIds()) { + Profile profile = results.get(uuid); + if (profile != null) { + profileMap.put(profile.getName(), uuid); + } else { + uuids.add(uuid.toString()); + } + } + } else { + for (UUID uuid : playerDomain.getUniqueIds()) { + uuids.add(uuid.toString()); + } + } + + final TextComponent.Builder builder = TextComponent.builder(""); + final Iterator profiles = profileMap.keySet().stream().sorted().map(name -> { + final UUID uuid = profileMap.get(name); + final TextComponent component = TextComponent.of(name, TextColor.YELLOW) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, uuid == null + ? TextComponent.of("Name only", TextColor.GRAY) + : TextComponent.of("Last known name of uuid: ", TextColor.GRAY) + .append(TextComponent.of(uuid.toString(), TextColor.WHITE)))); + if (uuid == null) { + return component; + } + return component.clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, uuid.toString())); + }).iterator(); + while (profiles.hasNext()) { + builder.append(profiles.next()); + if (profiles.hasNext() || !uuids.isEmpty()) { + builder.append(TextComponent.of(", ")); + } + } + + if (!uuids.isEmpty()) { + builder.append(TextComponent.of(uuids.size() + " unknown uuid" + (uuids.size() == 1 ? "" : "s"), TextColor.GRAY) + .hoverEvent(HoverEvent.of(HoverEvent.Action.SHOW_TEXT, TextComponent.of(String.join("\n", uuids)) + .append(TextComponent.newline().append(TextComponent.of("Click to select"))))) + .clickEvent(ClickEvent.of(ClickEvent.Action.SUGGEST_COMMAND, String.join(",", uuids)))); + } + + + return builder.build(); + } + + @Override + public boolean isDirty() { + return playerDomain.isDirty() || groupDomain.isDirty(); + } + + @Override + public void setDirty(boolean dirty) { + playerDomain.setDirty(dirty); + groupDomain.setDirty(dirty); + } + + @Override + public String toString() { + return "{players=" + playerDomain + + ", groups=" + groupDomain + + '}'; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Domain.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Domain.java new file mode 100644 index 000000000..8c3235aba --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/Domain.java @@ -0,0 +1,73 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +import com.sk89q.worldguard.LocalPlayer; + +import java.util.UUID; + +/** + * A domain contains a list of memberships. + */ +public interface Domain { + + /** + * Returns true if a domain contains a player. + * + * @param player the player to check + * @return whether this domain contains {@code player} + */ + boolean contains(LocalPlayer player); + + /** + * Returns true if a domain contains a player. + * + *

This method doesn't check for groups!

+ * + * @param uniqueId the UUID of the user + * @return whether this domain contains a player by that name + */ + boolean contains(UUID uniqueId); + + /** + * Returns true if a domain contains a player. + * + *

This method doesn't check for groups!

+ * + * @param playerName The name of the player to check + * @return whether this domain contains a player by that name + * @deprecated names are deprecated in MC 1.7+ in favor of UUIDs + */ + @Deprecated + boolean contains(String playerName); + + /** + * Get the number of entries. + * + * @return the number of entries + */ + int size(); + + /** + * Remove all entries. + */ + void clear(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java new file mode 100644 index 000000000..83d6d9c3f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/GroupDomain.java @@ -0,0 +1,151 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Contains groups in a domain. + */ +public class GroupDomain implements Domain, ChangeTracked { + + private final Set groups = new CopyOnWriteArraySet<>(); + private boolean dirty = true; + + /** + * Create a new instance. + */ + public GroupDomain() { + } + + /** + * Create a new instance with copies from another domain. + * + * @param domain the domain to copy values from + */ + public GroupDomain(GroupDomain domain) { + checkNotNull(domain, "domain"); + groups.addAll(domain.getGroups()); + } + + /** + * Create a new instance. + * + * @param groups an array of groups + */ + public GroupDomain(String[] groups) { + checkNotNull(groups); + for (String group : groups) { + addGroup(group); + } + } + + /** + * Add the name of the group to the domain. + * + * @param name the name of the group. + */ + public void addGroup(String name) { + checkNotNull(name); + if (!name.trim().isEmpty()) { + setDirty(true); + groups.add(name.trim().toLowerCase()); + } + } + + /** + * Remove the given group from the domain. + * + * @param name the name of the group + */ + public void removeGroup(String name) { + checkNotNull(name); + setDirty(true); + groups.remove(name.trim().toLowerCase()); + } + + @Override + public boolean contains(LocalPlayer player) { + checkNotNull(player); + for (String group : groups) { + if (player.hasGroup(group)) { + return true; + } + } + + return false; + } + + /** + * Get the set of group names. + * + * @return the set of group names + */ + public Set getGroups() { + return Collections.unmodifiableSet(groups); + } + + @Override + public boolean contains(UUID uniqueId) { + return false; // GroupDomains can't contain UUIDs + } + + @Override + public boolean contains(String playerName) { + return false; // GroupDomains can't contain player names. + } + + @Override + public int size() { + return groups.size(); + } + + @Override + public void clear() { + setDirty(true); + groups.clear(); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + @Override + public String toString() { + return "{" + + "names=" + groups + + '}'; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java new file mode 100644 index 000000000..084946219 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/domains/PlayerDomain.java @@ -0,0 +1,216 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.util.ChangeTracked; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArraySet; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores players (only) in a domain. + */ +public class PlayerDomain implements Domain, ChangeTracked { + + private final Set uniqueIds = new CopyOnWriteArraySet<>(); + private final Set names = new CopyOnWriteArraySet<>(); + private boolean dirty = true; + + /** + * Create a new instance. + */ + public PlayerDomain() { + } + + /** + * Create a new instance. + * + * @param domain the domain to copy values from + */ + public PlayerDomain(PlayerDomain domain) { + checkNotNull(domain, "domain"); + uniqueIds.addAll(domain.getUniqueIds()); + names.addAll(domain.getPlayers()); + dirty = true; + } + + /** + * Create a new instance with the given names. + * + * @param names an array of names + * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ + */ + @Deprecated + public PlayerDomain(String[] names) { + for (String name : names) { + addPlayer(name); + } + } + + /** + * Add the given player to the domain, identified by the player's name. + * + * @param name the name of the player + * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ + */ + @Deprecated + public void addPlayer(String name) { + checkNotNull(name); + if (!name.trim().isEmpty()) { + setDirty(true); + names.add(name.trim().toLowerCase()); + // Trim because some names contain spaces (previously valid Minecraft + // names) and we cannot store these correctly in the SQL storage + // implementations + } + } + + /** + * Add the given player to the domain, identified by the player's UUID. + * + * @param uniqueId the UUID of the player + */ + public void addPlayer(UUID uniqueId) { + checkNotNull(uniqueId); + setDirty(true); + uniqueIds.add(uniqueId); + } + + /** + * Add the given player to the domain, identified by the player's UUID. + * + * @param player the player + */ + public void addPlayer(LocalPlayer player) { + checkNotNull(player); + setDirty(true); + addPlayer(player.getUniqueId()); + } + + /** + * Remove the given player from the domain, identified by the player's name. + * + * @param name the name of the player + * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ + */ + @Deprecated + public void removePlayer(String name) { + checkNotNull(name); + setDirty(true); + names.remove(name.trim().toLowerCase()); + } + + /** + * Remove the given player from the domain, identified by the player's UUID. + * + * @param uuid the UUID of the player + */ + public void removePlayer(UUID uuid) { + checkNotNull(uuid); + setDirty(true); + uniqueIds.remove(uuid); + } + + /** + * Remove the given player from the domain, identified by either the + * player's name, the player's unique ID, or both. + * + * @param player the player + */ + public void removePlayer(LocalPlayer player) { + checkNotNull(player); + setDirty(true); + removePlayer(player.getName()); + removePlayer(player.getUniqueId()); + } + + @Override + public boolean contains(LocalPlayer player) { + checkNotNull(player); + return contains(player.getUniqueId()) || (!names.isEmpty() && contains(player.getName())); + } + + /** + * Get the set of player names. + * + * @return the set of player names + * @deprecated names are deprecated in favor of UUIDs in MC 1.7+ + */ + @Deprecated + public Set getPlayers() { + return Collections.unmodifiableSet(names); + } + + /** + * Get the set of player UUIDs. + * + * @return the set of player UUIDs + */ + public Set getUniqueIds() { + return Collections.unmodifiableSet(uniqueIds); + } + + @Override + public boolean contains(UUID uniqueId) { + checkNotNull(uniqueId); + return uniqueIds.contains(uniqueId); + } + + @Override + public boolean contains(String playerName) { + checkNotNull(playerName); + return names.contains(playerName.trim().toLowerCase()); + } + + @Override + public int size() { + return names.size() + uniqueIds.size(); + } + + @Override + public void clear() { + setDirty(true); + uniqueIds.clear(); + names.clear(); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + @Override + public String toString() { + return "{" + + "uuids=" + uniqueIds + + ", names=" + names + + '}'; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/PermissionModel.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/PermissionModel.java new file mode 100644 index 000000000..3b52b1005 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/PermissionModel.java @@ -0,0 +1,24 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal; + +public interface PermissionModel { + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/AbstractPermissionModel.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/AbstractPermissionModel.java new file mode 100644 index 000000000..b116f6682 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/AbstractPermissionModel.java @@ -0,0 +1,44 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.permission; + +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.internal.PermissionModel; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class AbstractPermissionModel implements PermissionModel { + + private final Actor sender; + + protected AbstractPermissionModel(Actor sender) { + checkNotNull(sender); + this.sender = sender; + } + + public Actor getSender() { + return sender; + } + + protected boolean hasPluginPermission(String permission) { + return getSender().hasPermission("worldguard." + permission); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java new file mode 100644 index 000000000..4fc45ecba --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/permission/RegionPermissionModel.java @@ -0,0 +1,199 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.permission; + +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; + +/** + * Used for querying region-related permissions. + */ +public class RegionPermissionModel extends AbstractPermissionModel { + + public RegionPermissionModel(Actor sender) { + super(sender); + } + + /** + * @deprecated Check {@code WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass(..)} instead + */ + @Deprecated + public boolean mayIgnoreRegionProtection(World world) { + if (getSender() instanceof LocalPlayer) + return WorldGuard.getInstance().getPlatform().getSessionManager().hasBypass((LocalPlayer) getSender(), world); + return hasPluginPermission("region.bypass." + world.getName()); + } + + public boolean mayForceLoadRegions() { + return hasPluginPermission("region.load"); + } + + public boolean mayForceSaveRegions() { + return hasPluginPermission("region.save"); + } + + public boolean mayMigrateRegionStore() { + return hasPluginPermission("region.migratedb"); + } + + public boolean mayMigrateRegionNames() { + return hasPluginPermission("region.migrateuuid"); + } + + public boolean mayDefine() { + return hasPluginPermission("region.define"); + } + + public boolean mayRedefine(ProtectedRegion region) { + return hasPatternPermission("redefine", region); + } + + public boolean mayClaim() { + return hasPluginPermission("region.claim"); + } + + public boolean mayClaimRegionsUnbounded() { + return hasPluginPermission("region.unlimited"); + } + + public boolean mayDelete(ProtectedRegion region) { + return hasPatternPermission("remove", region); + } + + public boolean maySetPriority(ProtectedRegion region) { + return hasPatternPermission("setpriority", region); + } + + public boolean maySetParent(ProtectedRegion child, ProtectedRegion parent) { + return hasPatternPermission("setparent", child) && + (parent == null || + hasPatternPermission("setparent", parent)); + } + + public boolean maySelect(ProtectedRegion region) { + return hasPatternPermission("select", region); + } + + public boolean mayLookup(ProtectedRegion region) { + return hasPatternPermission("info", region); + } + + public boolean mayTeleportTo(ProtectedRegion region) { + return hasPatternPermission("teleport", region); + } + + public boolean mayOverrideLocationFlagBounds(ProtectedRegion region) { + return hasPatternPermission("locationoverride", region); + } + + public boolean mayList() { + return hasPluginPermission("region.list"); + } + + public boolean mayList(String targetPlayer) { + if (targetPlayer == null) { + return mayList(); + } + + if (targetPlayer.equalsIgnoreCase(getSender().getName())) { + return hasPluginPermission("region.list.own"); + } else { + return mayList(); + } + } + + public boolean maySetFlag(ProtectedRegion region) { + return hasPatternPermission("flag.regions", region); + } + + public boolean maySetFlag(ProtectedRegion region, Flag flag) { + // This is a WTF permission + return hasPatternPermission( + "flag.flags." + flag.getName().toLowerCase(), region); + } + + public boolean maySetFlag(ProtectedRegion region, Flag flag, @Nullable String value) { + String sanitizedValue; + + if (value != null) { + sanitizedValue = value.trim().toLowerCase().replaceAll("[^a-z0-9]", ""); + if (sanitizedValue.length() > 20) { + sanitizedValue = sanitizedValue.substring(0, 20); + } + } else { + sanitizedValue = "unset"; + } + + // This is a WTF permission + return hasPatternPermission( + "flag.flags." + flag.getName().toLowerCase() + "." + sanitizedValue, region); + } + + public boolean mayAddMembers(ProtectedRegion region) { + return hasPatternPermission("addmember", region); + } + + public boolean mayAddOwners(ProtectedRegion region) { + return hasPatternPermission("addowner", region); + } + + public boolean mayRemoveMembers(ProtectedRegion region) { + return hasPatternPermission("removemember", region); + } + + public boolean mayRemoveOwners(ProtectedRegion region) { + return hasPatternPermission("removeowner", region); + } + + /** + * Checks to see if the given sender has permission to modify the given region + * using the region permission pattern. + * + * @param perm the name of the node + * @param region the region + */ + private boolean hasPatternPermission(String perm, ProtectedRegion region) { + if (!(getSender() instanceof Player)) { + return true; // Non-players (i.e. console, command blocks, etc.) have full power + } + + String idLower = region.getId().toLowerCase(); + String effectivePerm; + + if (region.isOwner((LocalPlayer) getSender())) { + return hasPluginPermission("region." + perm + ".own." + idLower) || + hasPluginPermission("region." + perm + ".member." + idLower); + } else if (region.isMember((LocalPlayer) getSender())) { + return hasPluginPermission("region." + perm + ".member." + idLower); + } else { + effectivePerm = "region." + perm + "." + idLower; + } + + return hasPluginPermission(effectivePerm); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/DebugHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/DebugHandler.java new file mode 100644 index 000000000..70beafd5b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/DebugHandler.java @@ -0,0 +1,35 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.platform; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.LocalPlayer; + +public interface DebugHandler { + + void testBreak(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException; + + void testPlace(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException; + + void testInteract(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException; + + void testDamage(Actor sender, LocalPlayer target, boolean fromTarget, boolean stackTraceMode) throws CommandException; +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/StringMatcher.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/StringMatcher.java new file mode 100644 index 000000000..28234896b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/StringMatcher.java @@ -0,0 +1,174 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.platform; + +import com.google.common.collect.Lists; +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.List; + +public interface StringMatcher { + + /** + * Match a world. + * + * The filter string syntax is as follows: + * #main returns the main world + * #normal returns the first world with a normal environment + * #nether return the first world with a nether environment + * #player:[name] returns the world that a player named {@code name} is located in, if the player is online. + * [name] A world with the name {@code name} + * + * @param sender The sender requesting a match + * @param filter The filter string + * @return The resulting world + * @throws CommandException if no world matches + */ + World matchWorld(Actor sender, String filter) throws CommandException; + + /** + * Match player names. + * + * The filter string uses the following format: + * \@[name] looks up all players with the exact {@code name} + * *[name] matches any player whose name contains {@code name} + * [name] matches any player whose name starts with {@code name} + * + * @param filter The filter string to check. + * @return A {@link List} of players who match {@code filter} + */ + List matchPlayerNames(String filter); + + /** + * Checks if the given list of players is greater than size 0, otherwise + * throw an exception. + * + * @param players The {@link List} to check + * @return {@code players} as an {@link Iterable} + * @throws CommandException If {@code players} is empty + */ + default Iterable checkPlayerMatch(List players) throws CommandException { + // Check to see if there were any matches + if (players.isEmpty()) { + throw new CommandException("No players matched query."); + } + + return players; + } + + /** + * Matches players based on the specified filter string + * + * The filter string format is as follows: + * * returns all the players currently online + * If {@code sender} is a {@link Player}: + * #world returns all players in the world that {@code sender} is in + * #near reaturns all players within 30 blocks of {@code sender}'s location + * Otherwise, the format is as specified in {@link #matchPlayerNames(String)} + * + * @param source The CommandSender who is trying to find a player + * @param filter The filter string for players + * @return iterator for players + * @throws CommandException if no matches are found + */ + Iterable matchPlayers(Actor source, String filter) throws CommandException; + + /** + * Match only a single player. + * + * @param sender The {@link Actor} who is requesting a player match + * @param filter The filter string. + * @see #matchPlayers(LocalPlayer) for filter string syntax + * @return The single player + * @throws CommandException If more than one player match was found + */ + default LocalPlayer matchSinglePlayer(Actor sender, String filter) throws CommandException { + // This will throw an exception if there are no matches + Iterator players = matchPlayers(sender, filter).iterator(); + + LocalPlayer match = players.next(); + + // We don't want to match the wrong person, so fail if if multiple + // players were found (we don't want to just pick off the first one, + // as that may be the wrong player) + if (players.hasNext()) { + throw new CommandException("More than one player found! " + + "Use @ for exact matching."); + } + + return match; + } + + /** + * Match only a single player or console. + * + * The filter string syntax is as follows: + * #console, *console, or ! return the server console + * All syntax from {@link #matchSinglePlayer(Actor, String)} + * @param sender The sender trying to match a CommandSender + * @param filter The filter string + * @return The resulting CommandSender + * @throws CommandException if either zero or more than one player matched. + */ + Actor matchPlayerOrConsole(Actor sender, String filter) throws CommandException; + + /** + * Get a single player as an iterator for players. + * + * @param player The player to return in an Iterable + * @return iterator for player + */ + default Iterable matchPlayers(LocalPlayer player) { + return Lists.newArrayList(player); + } + + /** + * Gets a world by name, if possible. + * + * @param worldName The name + * @return The world + */ + @Nullable + World getWorldByName(String worldName); + + /** + * Replace macros in the text. + * + * The macros replaced are as follows: + * %name%: The name of {@code sender}. + * %id%: The unique name of the sender. + * %online%: The number of players currently online on the server + * If {@code sender} is a Player: + * %world%: The name of the world {@code sender} is located in + * %health%: The health of {@code sender}. + * + * @param sender The sender to check + * @param message The message to replace macros in + * @return The message with macros replaced + */ + String replaceMacros(Actor sender, String message); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java new file mode 100644 index 000000000..71e26a4d6 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/platform/WorldGuardPlatform.java @@ -0,0 +1,172 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.platform; + +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.report.ReportList; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import com.sk89q.worldguard.session.SessionManager; +import com.sk89q.worldguard.util.profile.cache.ProfileCache; +import com.sk89q.worldguard.util.profile.resolver.ProfileService; + +import javax.annotation.Nullable; +import java.nio.file.Path; + +/** + * A platform for implementing. + */ +public interface WorldGuardPlatform { + + /** + * Gets the name of the platform. + * + * @return The platform name + */ + String getPlatformName(); + + /** + * Gets the version of the platform. + * + * @return The platform version + */ + String getPlatformVersion(); + + /** + * Notifies the platform when a flag context is created. + * + * @param flagContextBuilder The flag context + */ + void notifyFlagContextCreate(FlagContext.FlagContextBuilder flagContextBuilder); + + /** + * Get the global ConfigurationManager. + * Use this to access global configuration values and per-world configuration values. + * + * @return The global ConfigurationManager + */ + ConfigurationManager getGlobalStateManager(); + + /** + * Gets an instance of the matcher, which handles matching + * worlds, players, colours, etc from strings. + * + * @return The matcher + */ + StringMatcher getMatcher(); + + /** + * Gets the session manager. + * + * @return The session manager + */ + SessionManager getSessionManager(); + + /** + * Notifies all with the worldguard.notify permission. + * This will check both superperms and WEPIF, + * but makes sure WEPIF checks don't result in duplicate notifications + * + * @param message The notification to broadcast + */ + void broadcastNotification(String message); + + /** + * Notifies all with the worldguard.notify permission. + * This will check both superperms and WEPIF, + * but makes sure WEPIF checks don't result in duplicate notifications + * + * @param component The notification to broadcast + */ + void broadcastNotification(TextComponent component); + + /** + * Load the platform + */ + void load(); + + /** + * Unload the platform + */ + void unload(); + + /** + * Gets a RegionContainer. + * + * @return The region container + */ + RegionContainer getRegionContainer(); + + /** + * Gets the handler for debug commands. + * + * @return The debug handler + */ + DebugHandler getDebugHandler(); + + /** + * Gets the servers default game mode. + * + * @return The default game mode + */ + GameMode getDefaultGameMode(); + + /** + * Gets the configuration directory. + * + * @return The config directory + */ + Path getConfigDir(); + + /** + * Stack the inventory of the player + * + * @param localPlayer The player + */ + void stackPlayerInventory(LocalPlayer localPlayer); + + /** + * Adds reports specific to this platform. + * + * @param report The reportlist + */ + void addPlatformReports(ReportList report); + + /** + * Internal use. + */ + ProfileService createProfileService(ProfileCache profileCache); + + /** + * Get a region that encompasses the Vanilla spawn protection for the given world, if applicable. + * + * @param world world to check spawn protection of + * @return a region, or null if not applicable + */ + @Nullable + default ProtectedRegion getSpawnProtection(World world) { + return null; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/util/sql/StatementUtils.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/util/sql/StatementUtils.java new file mode 100644 index 000000000..7ac27ae35 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/internal/util/sql/StatementUtils.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.internal.util.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public final class StatementUtils { + + private StatementUtils() { + } + + /** + * Creates a comma separated list of PreparedStatement place holders + * + * @param length The number of wildcards to create + * @return A string with {@code length} wildcards for usage in a PreparedStatement + */ + public static String preparePlaceHolders(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length;) { + builder.append("?"); + if (++i < length) { + builder.append(","); + } + } + return builder.toString(); + } + + /** + * Adds all of the parsed values to the PreparedStatement + * + * @param preparedStatement The preparedStanement to add to + * @param values The values to set + * @throws SQLException see {@link PreparedStatement#setString(int, String)} + */ + public static void setValues(PreparedStatement preparedStatement, String... values) throws SQLException { + for (int i = 0; i < values.length; i++) { + preparedStatement.setString(i + 1, values[i]); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/AbstractRegionSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/AbstractRegionSet.java new file mode 100644 index 000000000..ea78e6854 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/AbstractRegionSet.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import static com.sk89q.worldguard.protection.flags.StateFlag.test; + +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; + +import javax.annotation.Nullable; + +public abstract class AbstractRegionSet implements ApplicableRegionSet { + + @Override + public boolean testState(@Nullable RegionAssociable subject, StateFlag... flags) { + return test(queryState(subject, flags)); + } + + @Nullable + @Override + public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) { + State value = null; + + for (StateFlag flag : flags) { + value = StateFlag.combine(value, queryValue(subject, flag)); + if (value == State.DENY) { + break; + } + } + + return value; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java new file mode 100644 index 000000000..845931d08 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/ApplicableRegionSet.java @@ -0,0 +1,215 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Set; + +/** + * Represents the effective set of flags, owners, and members for a given + * spatial query. + * + *

An instance of this can be created using the spatial query methods + * available on {@link RegionManager}.

+ */ +public interface ApplicableRegionSet extends Iterable { + + /** + * Return whether this region set is a virtual set. A virtual set + * does not contain real results. + * + *

A virtual result may be returned if region data failed to load or + * there was some special exception (i.e. the region bypass permission). + *

+ * + *

Be sure to check the value of this flag if an instance of this + * interface is being retrieved from RegionQuery as it may + * return an instance of {@link PermissiveRegionSet} or + * {@link FailedLoadRegionSet}, among other possibilities.

+ * + * @return true if loaded + * @see FailedLoadRegionSet + */ + boolean isVirtual(); + + /** + * Test whether the (effective) value for a list of state flags equals + * {@code ALLOW}. + * + *

{@code subject} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The subject argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + * @param subject an optional subject, which would be used to determine the region groups that apply + * @param flags a list of flags to check + * @return true if the result was {@code ALLOW} + * @see #queryState(RegionAssociable, StateFlag...) + */ + boolean testState(@Nullable RegionAssociable subject, StateFlag... flags); + + /** + * Get the (effective) value for a list of state flags. The rules of + * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, + * and {@code ALLOW} overrides {@code NONE}. One flag may override another. + * + *

{@code subject} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The subject argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + * @param subject an optional subject, which would be used to determine the region groups that apply + * @param flags a list of flags to check + * @return a state + */ + @Nullable + State queryState(@Nullable RegionAssociable subject, StateFlag... flags); + + /** + * Get the effective value for a flag. If there are multiple values + * (for example, multiple overlapping regions with + * the same priority may have the same flag set), then the selected + * (or "winning") value will depend on the flag type. + * + *

Only some flag types actually have a strategy for picking the + * "best value." For most types, the actual value that is chosen to be + * returned is undefined (it could be any value). As of writing, the only + * type of flag that actually has a strategy for picking a value is the + * {@link StateFlag}.

+ * + *

{@code subject} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The subject argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag + * @return a value, which could be {@code null} + */ + @Nullable + V queryValue(@Nullable RegionAssociable subject, Flag flag); + + /** + * Get the effective value for a key in a {@link MapFlag}. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will be undefined. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag of type {@link MapFlag} + * @param key the key for the map flag + * @return a value, which could be {@code null} + */ + @Nullable + V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key); + + /** + * Get the effective value for a key in a {@link MapFlag}. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will be undefined. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag of type {@link MapFlag} + * @param key the key for the map flag + * @return a value, which could be {@code null} + */ + @Nullable + V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key, @Nullable Flag fallback); + + /** + * Get the effective values for a flag, returning a collection of all + * values. It is up to the caller to determine which value, if any, + * from the collection will be used. + * + *

{@code subject} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The subject argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag + * @return a collection of values + */ + Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag); + + /** + * Test whether a player is an owner of all regions in this set. + * + * @param player the player + * @return whether the player is an owner of all regions + */ + boolean isOwnerOfAll(LocalPlayer player); + + /** + * Test whether a player is an owner or member of all regions in this set. + * + * @param player the player + * @return whether the player is a member of all regions + */ + boolean isMemberOfAll(LocalPlayer player); + + /** + * Get the number of regions that are included. + * + * @return the number of contained regions + */ + int size(); + + /** + * Get an immutable set of regions that are included in this set. + * + * @return a set of regions + */ + Set getRegions(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/DelayedRegionOverlapAssociation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/DelayedRegionOverlapAssociation.java new file mode 100644 index 000000000..c362d0927 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/DelayedRegionOverlapAssociation.java @@ -0,0 +1,47 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.protection.regions.RegionQuery; + +import java.util.List; + +/** + * Determines that the association to a region is {@code OWNER} if the input + * region is in a set of source regions. + * + *

This class only performs a spatial query if its + * {@link #getAssociation(List)} method is called.

+ * + * @deprecated Use {@link com.sk89q.worldguard.protection.association.DelayedRegionOverlapAssociation} instead. This class is mis-packaged. + */ +@Deprecated +public class DelayedRegionOverlapAssociation extends com.sk89q.worldguard.protection.association.DelayedRegionOverlapAssociation { + /** + * Create a new instance. + * @param query the query + * @param location the location + */ + public DelayedRegionOverlapAssociation(RegionQuery query, Location location) { + super(query, location, false); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FailedLoadRegionSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FailedLoadRegionSet.java new file mode 100644 index 000000000..71b99e059 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FailedLoadRegionSet.java @@ -0,0 +1,129 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * A region set that is to be used when region data has failed. Operations + * are blocked. + */ +public class FailedLoadRegionSet extends AbstractRegionSet { + + private static final FailedLoadRegionSet INSTANCE = new FailedLoadRegionSet(); + + private final String denyMessage = "Region data for WorldGuard failed to load for this world, so " + + "everything has been protected as a precaution. Please inform a server administrator."; + private final Collection denyMessageCollection = ImmutableList.of(denyMessage); + + private FailedLoadRegionSet() { + } + + @Override + public boolean isVirtual() { + return true; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + if (flag == Flags.BUILD) { + return (V) State.DENY; + } else if (flag == Flags.DENY_MESSAGE) { + return (V) denyMessage; + } + return flag.getDefault(); + } + + @Nullable + @Override + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key) { + return queryMapValue(subject, flag, key, null); + } + + @Nullable + @Override + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key, @Nullable Flag fallback) { + Map defaultVal = flag.getDefault(); + return defaultVal != null ? defaultVal.get(key) : fallback != null ? fallback.getDefault() : null; + } + + @SuppressWarnings("unchecked") + @Override + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { + if (flag == Flags.BUILD) { + return (Collection) ImmutableList.of(State.DENY); + } else if (flag == Flags.DENY_MESSAGE) { + return (Collection) denyMessageCollection; + } + V fallback = flag.getDefault(); + return fallback != null ? ImmutableList.of(fallback) : (Collection) ImmutableList.of(); + } + + @Override + public boolean isOwnerOfAll(LocalPlayer player) { + return false; + } + + @Override + public boolean isMemberOfAll(LocalPlayer player) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public Set getRegions() { + return Collections.emptySet(); + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + /** + * Get an instance. + * + * @return an instance + */ + public static FailedLoadRegionSet getInstance() { + return INSTANCE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java new file mode 100644 index 000000000..c6cdc189b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/FlagValueCalculator.java @@ -0,0 +1,582 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.common.collect.Sets; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.NormativeOrders; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Calculates the value of a flag given a list of regions and an optional + * global region. + * + *

Since there may be multiple overlapping regions, regions with + * differing priorities, regions with inheritance, flags with region groups + * assigned to them, and much more, the task of calculating the "effective" + * value of a flag is far from trivial. This class abstracts away the + * difficult with a number of methods for performing these calculations.

+ */ +public class FlagValueCalculator { + + @Nullable + private final ProtectedRegion globalRegion; + private final Iterable applicable; + + /** + * Create a new instance. + * + * @param regions a list of applicable regions that must be sorted according to {@link NormativeOrders} + * @param globalRegion an optional global region (null to not use one) + */ + public FlagValueCalculator(List regions, @Nullable ProtectedRegion globalRegion) { + checkNotNull(regions); + + this.globalRegion = globalRegion; + + applicable = globalRegion == null ? regions + : Iterables.concat(regions, Collections.singletonList(globalRegion)); + } + + /** + * Returns an iterable of regions sorted by priority (descending), with + * the global region tacked on at the end if one exists. + * + * @return an iterable + */ + private Iterable getApplicable() { + return applicable; + } + + /** + * Return the membership status of the given subject, indicating + * whether there are no (counted) regions in the list of regions, + * whether the subject is a member of all (counted) regions, or + * whether the subject is not a member of all (counted) regions. + * + *

A region is "counted" if it doesn't have the + * {@link Flags#PASSTHROUGH} flag set to {@code ALLOW} and if + * there isn't another "counted" region with a higher priority. + * (The explicit purpose of the PASSTHROUGH flag is to have the + * region be skipped over in this check.)

+ * + *

This method is mostly for internal use. It's not particularly + * useful.

+ * + * @param subject the subject + * @return the membership result + */ + public Result getMembership(RegionAssociable subject) { + checkNotNull(subject); + + int minimumPriority = Integer.MIN_VALUE; + Result result = Result.NO_REGIONS; + + Set ignoredRegions = Sets.newHashSet(); + + for (ProtectedRegion region : getApplicable()) { + int priority = getPriority(region); + + // Don't consider lower priorities below minimumPriority + // (which starts at Integer.MIN_VALUE). A region that "counts" + // (has the flag set OR has members) will raise minimumPriority + // to its own priority. + if (priority < minimumPriority) { + break; + } + + // If PASSTHROUGH is set, ignore this region + if (getEffectiveFlag(region, Flags.PASSTHROUGH, subject) == State.ALLOW) { + continue; + } + + if (ignoredRegions.contains(region)) { + continue; + } + + minimumPriority = priority; + + boolean member = RegionGroup.MEMBERS.contains(subject.getAssociation(Collections.singletonList(region))); + + if (member) { + result = Result.SUCCESS; + addParents(ignoredRegions, region); + } else { + return Result.FAIL; + } + } + + return result; + } + + + /** + * Get the effective value for a list of state flags. The rules of + * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, + * and {@code ALLOW} overrides {@code NONE}. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flags a list of flags to check + * @return a state + */ + @Nullable + public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) { + State value = null; + + for (StateFlag flag : flags) { + value = StateFlag.combine(value, queryValue(subject, flag)); + if (value == State.DENY) { + break; + } + } + + return value; + } + + /** + * Get the effective value for a list of state flags. The rules of + * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, + * and {@code ALLOW} overrides {@code NONE}. + * + *

This method is the same as + * {@link #queryState(RegionAssociable, StateFlag...)}.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag a flag to check + * @return a state + */ + @Nullable + public State queryState(@Nullable RegionAssociable subject, StateFlag flag) { + return queryValue(subject, flag); + } + + /** + * Get the effective value for a flag. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will depend + * on the flag type. + * + *

Only some flag types actually have a strategy for picking the + * "best value." For most types, the actual value that is chosen to be + * returned is undefined (it could be any value). As of writing, the only + * type of flag that can consistently return the same 'best' value is + * {@link StateFlag}.

+ * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag + * @return a value, which could be {@code null} + */ + @Nullable + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + Collection values = queryAllValues(subject, flag, true); + return flag.chooseValue(values); + } + + /** + * Get the effective value for a key in a {@link MapFlag}. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will be undefined. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag of type {@link MapFlag} + * @param key the key for the map flag + * @return a value, which could be {@code null} + */ + @Nullable + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key, Flag fallback) { + checkNotNull(flag); + checkNotNull(key); + + Map consideredValues = new HashMap<>(); + Map fallbackValues = new HashMap<>(); + int minimumPriority = Integer.MIN_VALUE; + Set ignoredParents = new HashSet<>(); + + for(ProtectedRegion region : getApplicable()) { + int priority = getPriority(region); + + if (priority < minimumPriority) { + break; + } + + if (ignoredParents.contains(region)) { + continue; + } + + V effectiveValue = getEffectiveMapValue(region, flag, key, subject); + + if (effectiveValue != null) { + minimumPriority = priority; + consideredValues.put(region, effectiveValue); + } else if (fallback != null) { + effectiveValue = getEffectiveFlag(region, fallback, subject); + if (effectiveValue != null) { + minimumPriority = priority; + fallbackValues.put(region, effectiveValue); + } + } + + addParents(ignoredParents, region); + } + + + if (consideredValues.isEmpty()) { + if (fallback != null && !fallbackValues.isEmpty()) { + return fallback.chooseValue(fallbackValues.values()); + } + V defaultValue = flag.getValueFlag().getDefault(); + return defaultValue != null ? defaultValue : fallback != null ? fallback.getDefault() : null; + } + + return flag.getValueFlag().chooseValue(consideredValues.values()); + } + + @Nullable + public V getEffectiveMapValue(ProtectedRegion region, MapFlag mapFlag, K key, RegionAssociable subject) { + return getEffectiveMapValueOf(region, mapFlag, key, subject); + } + + @Nullable + public static V getEffectiveMapValueOf(ProtectedRegion region, MapFlag mapFlag, K key, RegionAssociable subject) { + List seen = new ArrayList<>(); + ProtectedRegion current = region; + + while (current != null) { + seen.add(current); + + Map mapValue = current.getFlag(mapFlag); + + if (mapValue != null && mapValue.containsKey(key)) { + boolean use = true; + + if (mapFlag.getRegionGroupFlag() != null) { + RegionGroup group = current.getFlag(mapFlag.getRegionGroupFlag()); + if (group == null) { + group = mapFlag.getRegionGroupFlag().getDefault(); + } + + if (group == null) { + use = false; + } else if (subject == null) { + use = group.contains(Association.NON_MEMBER); + } else if (!group.contains(subject.getAssociation(seen))) { + use = false; + } + } + + if (use) { + return mapValue.get(key); + } + } + + current = current.getParent(); + } + return null; + } + + /** + * Get the effective values for a flag, returning a collection of all + * values. It is up to the caller to determine which value, if any, + * from the collection will be used. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag + * @return a collection of values + */ + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { + return queryAllValues(subject, flag, false); + } + + /** + * Get the effective values for a flag, returning a collection of all + * values. It is up to the caller to determine which value, if any, + * from the collection will be used. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag + * @param acceptOne if possible, return only one value if it doesn't matter + * @return a collection of values + */ + @SuppressWarnings("unchecked") + private Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag, boolean acceptOne) { + checkNotNull(flag); + + // Can't use this optimization with flags that have a conflict resolution strategy + if (acceptOne && flag.hasConflictStrategy()) { + acceptOne = false; + } + + // Check to see whether we have a subject if this is BUILD + if (flag.requiresSubject() && subject == null) { + throw new NullPointerException("The " + flag.getName() + " flag is handled in a special fashion and requires a non-null subject parameter"); + } + + int minimumPriority = Integer.MIN_VALUE; + + Map consideredValues = new HashMap<>(); + Set ignoredParents = new HashSet<>(); + + for (ProtectedRegion region : getApplicable()) { + int priority = getPriority(region); + + if (priority < minimumPriority) { + break; + } + + if (ignoredParents.contains(region)) { + continue; + } + + V value = getEffectiveFlag(region, flag, subject); + + if (value != null) { + minimumPriority = priority; + + if (acceptOne) { + return Arrays.asList(value); + } else { + consideredValues.put(region, value); + } + } + + addParents(ignoredParents, region); + + // The BUILD flag is implicitly set on every region where + // PASSTHROUGH is not set to ALLOW + if (priority != minimumPriority && flag.implicitlySetWithMembership() + && getEffectiveFlag(region, Flags.PASSTHROUGH, subject) != State.ALLOW) { + minimumPriority = priority; + } + } + + if (flag.usesMembershipAsDefault() && consideredValues.isEmpty()) { + switch (getMembership(subject)) { + case FAIL: + return ImmutableList.of(); + case SUCCESS: + return (Collection) ImmutableList.of(State.ALLOW); + } + } + + if (consideredValues.isEmpty()) { + V fallback = flag.getDefault(); + return fallback != null ? ImmutableList.of(fallback) : (Collection) ImmutableList.of(); + } + + return consideredValues.values(); + } + + /** + * Get the effective priority of a region, overriding a region's priority + * when appropriate (i.e. with the global region). + * + * @param region the region + * @return the priority + */ + public int getPriority(final ProtectedRegion region) { + return getPriorityOf(region); + } + + public static int getPriorityOf(final ProtectedRegion region) { + if (region.getId().equals(ProtectedRegion.GLOBAL_REGION)) { + return Integer.MIN_VALUE; + } else { + return region.getPriority(); + } + } + + /** + * Get a region's state flag, checking parent regions until a value for the + * flag can be found (if one even exists). + * + * @param region the region + * @param flag the flag + * @param subject an subject object + * @return the value + */ + @Nullable + public V getEffectiveFlag(final ProtectedRegion region, Flag flag, @Nullable RegionAssociable subject) { + return getEffectiveFlagOf(region, flag, subject); + } + + @SuppressWarnings("unchecked") + @Nullable + public static V getEffectiveFlagOf(final ProtectedRegion region, Flag flag, @Nullable RegionAssociable subject) { + if (region.getId().equals(ProtectedRegion.GLOBAL_REGION)) { + if (flag == Flags.PASSTHROUGH) { + // Has members/owners -> the global region acts like + // a regular region without PASSTHROUGH + State passthrough = region.getFlag(Flags.PASSTHROUGH); + if (passthrough == State.DENY || passthrough != State.ALLOW && region.hasMembersOrOwners()) { + return null; + } else { + return (V) State.ALLOW; + } + + } else if (flag instanceof StateFlag && ((StateFlag) flag).preventsAllowOnGlobal()) { + // Legacy behavior -> we can't let people change BUILD on + // the global region + State value = region.getFlag((StateFlag) flag); + return value != State.ALLOW ? (V) value : null; + } + } + + ProtectedRegion current = region; + + List seen = new ArrayList<>(); + + while (current != null) { + seen.add(current); + + V value = current.getFlag(flag); + + if (value != null) { + boolean use = true; + + if (flag.getRegionGroupFlag() != null) { + RegionGroup group = current.getFlag(flag.getRegionGroupFlag()); + if (group == null) { + group = flag.getRegionGroupFlag().getDefault(); + } + + if (group == null) { + use = false; + } else if (subject == null) { + use = group.contains(Association.NON_MEMBER); + } else if (!group.contains(subject.getAssociation(seen))) { + use = false; + } + } + + if (use) { + return value; + } + } + + current = current.getParent(); + } + + return null; + } + + /** + * Clear a region's parents for getFlag(). + * + * @param ignored The regions to ignore + * @param region The region to start from + */ + private void addParents(Set ignored, ProtectedRegion region) { + ProtectedRegion parent = region.getParent(); + + while (parent != null) { + ignored.add(parent); + parent = parent.getParent(); + } + } + + /** + * Describes the membership result from + * {@link #getMembership(RegionAssociable)}. + */ + public static enum Result { + /** + * Indicates that there are no regions or the only regions are + * ones with {@link Flags#PASSTHROUGH} enabled. + */ + NO_REGIONS, + + /** + * Indicates that the player is not a member of all overlapping + * regions. + */ + FAIL, + + /** + * Indicates that the player is a member of all overlapping + * regions. + */ + SUCCESS + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/PermissiveRegionSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/PermissiveRegionSet.java new file mode 100644 index 000000000..bf800e5b8 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/PermissiveRegionSet.java @@ -0,0 +1,121 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * A virtual region result set that is highly permissive, considering everyone + * a member. Returned flag values are default values (when available). + */ +public class PermissiveRegionSet extends AbstractRegionSet { + + private static final PermissiveRegionSet INSTANCE = new PermissiveRegionSet(); + + private PermissiveRegionSet() { + } + + @Override + public boolean isVirtual() { + return true; + } + + @SuppressWarnings("unchecked") + @Nullable + @Override + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + if (flag == Flags.BUILD) { + return (V) State.DENY; + } + return flag.getDefault(); + } + + @Nullable + @Override + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key) { + return queryMapValue(subject, flag, key, null); + } + + @Nullable + @Override + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key, @Nullable Flag fallback) { + Map defaultVal = flag.getDefault(); + return defaultVal != null ? defaultVal.get(key) : fallback != null ? fallback.getDefault() : null; + } + + @SuppressWarnings("unchecked") + @Override + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { + if (flag == Flags.BUILD) { + return (Collection) ImmutableList.of(State.DENY); + } + V fallback = flag.getDefault(); + return fallback != null ? ImmutableList.of(fallback) : (Collection) ImmutableList.of(); + } + + @Override + public boolean isOwnerOfAll(LocalPlayer player) { + return true; + } + + @Override + public boolean isMemberOfAll(LocalPlayer player) { + return true; + } + + @Override + public int size() { + return 0; + } + + @Override + public Set getRegions() { + return Collections.emptySet(); + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + /** + * Get an instance. + * + * @return an instance + */ + public static PermissiveRegionSet getInstance() { + return INSTANCE; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/RegionResultSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/RegionResultSet.java new file mode 100644 index 000000000..f4346ddf0 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/RegionResultSet.java @@ -0,0 +1,186 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.google.common.collect.ImmutableSet; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.NormativeOrders; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An implementation that calculates flags using a list of regions. + */ +public class RegionResultSet extends AbstractRegionSet { + + private final List applicable; + private final FlagValueCalculator flagValueCalculator; + @Nullable + private Set regionSet; + + /** + * Create a new region result set. + * + *

The given list must not contain duplicates or the behavior of + * this instance will be undefined.

+ * + * @param applicable the regions contained in this set + * @param globalRegion the global region, set aside for special handling. + */ + public RegionResultSet(List applicable, @Nullable ProtectedRegion globalRegion) { + this(applicable, globalRegion, false); + } + + /** + * Create a new region result set. + * + * @param applicable the regions contained in this set + * @param globalRegion the global region, set aside for special handling. + */ + public RegionResultSet(Set applicable, @Nullable ProtectedRegion globalRegion) { + this(NormativeOrders.fromSet(applicable), globalRegion, true); + this.regionSet = ImmutableSet.copyOf(applicable); + } + + /** + * Create a new region result set. + * + *

The list of regions may be first sorted with + * {@link NormativeOrders}. If that is the case, {@code sorted} should be + * {@code true}. Otherwise, the list will be sorted in-place.

+ * + * @param applicable the regions contained in this set + * @param globalRegion the global region, set aside for special handling. + * @param sorted true if the list is already sorted with {@link NormativeOrders} + */ + public RegionResultSet(List applicable, @Nullable ProtectedRegion globalRegion, boolean sorted) { + checkNotNull(applicable); + if (!sorted) { + NormativeOrders.sort(applicable); + } + this.applicable = Collections.unmodifiableList(applicable); + this.flagValueCalculator = new FlagValueCalculator(applicable, globalRegion); + } + + @Override + public boolean isVirtual() { + return false; + } + + @Override + @Nullable + public State queryState(@Nullable RegionAssociable subject, StateFlag... flags) { + return flagValueCalculator.queryState(subject, flags); + } + + @Override + @Nullable + public V queryValue(@Nullable RegionAssociable subject, Flag flag) { + return flagValueCalculator.queryValue(subject, flag); + } + + @Override + public Collection queryAllValues(@Nullable RegionAssociable subject, Flag flag) { + return flagValueCalculator.queryAllValues(subject, flag); + } + + @Override + @Nullable + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key) { + return flagValueCalculator.queryMapValue(subject, flag, key, null); + } + + @Override + @Nullable + public V queryMapValue(@Nullable RegionAssociable subject, MapFlag flag, K key, Flag fallback) { + return flagValueCalculator.queryMapValue(subject, flag, key, fallback); + } + + @Override + public boolean isOwnerOfAll(LocalPlayer player) { + checkNotNull(player); + + for (ProtectedRegion region : applicable) { + if (!region.isOwner(player)) { + return false; + } + } + + return true; + } + + @Override + public boolean isMemberOfAll(LocalPlayer player) { + checkNotNull(player); + + for (ProtectedRegion region : applicable) { + if (!region.isMember(player)) { + return false; + } + } + + return true; + } + + @Override + public int size() { + return applicable.size(); + } + + @Override + public Set getRegions() { + if (regionSet != null) { + return regionSet; + } + regionSet = ImmutableSet.copyOf(applicable); + return regionSet; + } + + @Override + public Iterator iterator() { + return applicable.iterator(); + } + + /** + * Create a new instance using a list of regions that is known to + * already be sorted by priority descending. + * + * @param regions a list of regions + * @param globalRegion a global region + * @return an instance + */ + public static RegionResultSet fromSortedList(List regions, @Nullable ProtectedRegion globalRegion) { + return new RegionResultSet(regions, globalRegion, true); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/AbstractRegionOverlapAssociation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/AbstractRegionOverlapAssociation.java new file mode 100644 index 000000000..0cbd78fe0 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/AbstractRegionOverlapAssociation.java @@ -0,0 +1,127 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public abstract class AbstractRegionOverlapAssociation implements RegionAssociable { + + @Nullable + protected Set source; + private boolean useMaxPriorityAssociation; + private int maxPriority; + private Set maxPriorityRegions; + + protected AbstractRegionOverlapAssociation(@Nullable Set source, boolean useMaxPriorityAssociation) { + this.source = source; + this.useMaxPriorityAssociation = useMaxPriorityAssociation; + } + + protected void calcMaxPriority() { + checkNotNull(source); + int best = 0; + Set bestRegions = new HashSet<>(); + for (ProtectedRegion region : source) { + int priority = region.getPriority(); + if (priority > best) { + best = priority; + bestRegions.clear(); + bestRegions.add(region); + } else if (priority == best) { + bestRegions.add(region); + } + } + this.maxPriority = best; + this.maxPriorityRegions = bestRegions; + } + + private boolean checkNonplayerProtectionDomains(Iterable source, Collection domains) { + if (source == null || domains == null || domains.isEmpty()) { + return false; + } + + for (ProtectedRegion region : source) { + // Potential endless recurrence? No, because there is no region group flag. + Set regionDomains = FlagValueCalculator.getEffectiveFlagOf(region, Flags.NONPLAYER_PROTECTION_DOMAINS, this); + + if (regionDomains == null || regionDomains.isEmpty()) { + continue; + } + + if (!Collections.disjoint(regionDomains, domains)) { + return true; + } + } + + return false; + } + + @Override + public Association getAssociation(List regions) { + checkNotNull(source); + for (ProtectedRegion region : regions) { + while (region != null) { + if ((region.getId().equals(ProtectedRegion.GLOBAL_REGION) && source.isEmpty())) { + return Association.OWNER; + } + + if (source.contains(region)) { + if (useMaxPriorityAssociation) { + int priority = region.getPriority(); + if (priority == maxPriority) { + return Association.OWNER; + } + } else { + return Association.OWNER; + } + } + + Set source; + + if (useMaxPriorityAssociation) { + source = maxPriorityRegions; + } else { + source = this.source; + } + + // Potential endless recurrence? No, because there is no region group flag. + if (checkNonplayerProtectionDomains(source, FlagValueCalculator.getEffectiveFlagOf(region, Flags.NONPLAYER_PROTECTION_DOMAINS, this))) { + return Association.OWNER; + } + + region = region.getParent(); + } + } + + return Association.NON_MEMBER; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/Associables.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/Associables.java new file mode 100644 index 000000000..a111c1b96 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/Associables.java @@ -0,0 +1,58 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility methods to deal with associables. + */ +public final class Associables { + + private static final RegionAssociable OWNER_ASSOCIABLE = new ConstantAssociation(Association.OWNER); + private static final RegionAssociable MEMBER_ASSOCIABLE = new ConstantAssociation(Association.MEMBER); + private static final RegionAssociable NON_MEMBER_ASSOCIABLE = new ConstantAssociation(Association.NON_MEMBER); + + private Associables() { + } + + /** + * Get an instance that always returns the same association. + * + * @param association the association + * @return the instance + */ + public static RegionAssociable constant(Association association) { + checkNotNull(association); + switch (association) { + case OWNER: + return OWNER_ASSOCIABLE; + case MEMBER: + return MEMBER_ASSOCIABLE; + case NON_MEMBER: + return NON_MEMBER_ASSOCIABLE; + default: + return new ConstantAssociation(association); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java new file mode 100644 index 000000000..0f5f3a109 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/ConstantAssociation.java @@ -0,0 +1,40 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.List; + +class ConstantAssociation implements RegionAssociable { + + private final Association association; + + ConstantAssociation(Association association) { + this.association = association; + } + + @Override + public Association getAssociation(List regions) { + return association; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/DelayedRegionOverlapAssociation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/DelayedRegionOverlapAssociation.java new file mode 100644 index 000000000..05ac70fcb --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/DelayedRegionOverlapAssociation.java @@ -0,0 +1,79 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption; + +import java.util.List; + +/** + * Determines that the association to a region is {@code OWNER} if the input + * region is in a set of source regions. + * + *

This class only performs a spatial query if its + * {@link #getAssociation(List)} method is called.

+ */ +public class DelayedRegionOverlapAssociation extends AbstractRegionOverlapAssociation { + + private final RegionQuery query; + private final Location location; + + /** + * Create a new instance. + * @param query the query + * @param location the location + */ + public DelayedRegionOverlapAssociation(RegionQuery query, Location location) { + this(query, location, false); + } + + /** + * Create a new instance. + * @param query the query + * @param location the location + * @param useMaxPriorityAssociation whether to use the max priority from regions to determine association + */ + public DelayedRegionOverlapAssociation(RegionQuery query, Location location, boolean useMaxPriorityAssociation) { + super(null, useMaxPriorityAssociation); + checkNotNull(query); + checkNotNull(location); + this.query = query; + this.location = location; + } + + @Override + public Association getAssociation(List regions) { + if (source == null) { + ApplicableRegionSet result = query.getApplicableRegions(location, QueryOption.NONE); + source = result.getRegions(); + calcMaxPriority(); + } + + return super.getAssociation(regions); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java new file mode 100644 index 000000000..95b9c9f36 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionAssociable.java @@ -0,0 +1,40 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.domains.Association; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.List; + +/** + * An object that can have membership in a region. + */ +public interface RegionAssociable { + + /** + * Get the highest association level for the input regions. + * + * @param regions a list of regions + * @return the highest membership level + */ + Association getAssociation(List regions); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java new file mode 100644 index 000000000..95e07edb5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/association/RegionOverlapAssociation.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.association; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nonnull; +import java.util.Set; + +/** + * Determines that the association to a region is {@code OWNER} if the input + * region is in a set of source regions. + */ +public class RegionOverlapAssociation extends AbstractRegionOverlapAssociation { + + /** + * Create a new instance. + * + * @param source set of regions that input regions must be contained within + */ + public RegionOverlapAssociation(@Nonnull Set source) { + this(source, false); + } + + /** + * Create a new instance. + * + * @param source set of regions that input regions must be contained within + * @param useMaxPriorityAssociation whether to use the max priority from regions to determine association + */ + public RegionOverlapAssociation(@Nonnull Set source, boolean useMaxPriorityAssociation) { + super(source, useMaxPriorityAssociation); + calcMaxPriority(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BooleanFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BooleanFlag.java new file mode 100644 index 000000000..3d5145c96 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BooleanFlag.java @@ -0,0 +1,66 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * A boolean flag. + */ +public class BooleanFlag extends Flag { + + public BooleanFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public BooleanFlag(String name) { + super(name); + } + + @Override + public Boolean parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + + if (input.equalsIgnoreCase("true") || input.equalsIgnoreCase("yes") + || input.equalsIgnoreCase("on") + || input.equalsIgnoreCase("1")) { + return true; + } else if (input.equalsIgnoreCase("false") || input.equalsIgnoreCase("no") + || input.equalsIgnoreCase("off") + || input.equalsIgnoreCase("0")) { + return false; + } else { + throw new InvalidFlagFormat("Not a yes/no value: " + input); + } + } + + @Override + public Boolean unmarshal(Object o) { + if (o instanceof Boolean) { + return (Boolean) o; + } else { + return null; + } + } + + @Override + public Object marshal(Boolean o) { + return o; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BuildFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BuildFlag.java new file mode 100644 index 000000000..173983637 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/BuildFlag.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * A special implementation of the {@link StateFlag} for + * {@link Flags#BUILD}. + */ +class BuildFlag extends StateFlag { + + public BuildFlag(String name, boolean def) { + super(name, def); + } + + @Override + public boolean implicitlySetWithMembership() { + return true; + } + + @Override + public boolean usesMembershipAsDefault() { + return true; + } + + @Override + public boolean preventsAllowOnGlobal() { + return true; + } + + @Override + public boolean requiresSubject() { + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/CommandStringFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/CommandStringFlag.java new file mode 100644 index 000000000..dd3afc601 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/CommandStringFlag.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * Stores a command/ + */ +public class CommandStringFlag extends Flag { + + public CommandStringFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public CommandStringFlag(String name) { + super(name); + } + + @Override + public String parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + input = input.trim(); + if (!input.startsWith("/")) { + input = "/" + input; + } + return input.toLowerCase(); + } + + @Override + public String unmarshal(Object o) { + if (o instanceof String) { + return (String) o; + } else { + return null; + } + } + + @Override + public Object marshal(String o) { + return o; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/DoubleFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/DoubleFlag.java new file mode 100644 index 000000000..81b046971 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/DoubleFlag.java @@ -0,0 +1,55 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * Stores doubles. + */ +public class DoubleFlag extends NumberFlag { + + public DoubleFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public DoubleFlag(String name) { + super(name); + } + + @Override + public Double parseInput(FlagContext context) throws InvalidFlagFormat { + return context.getUserInputAsDouble(); + } + + @Override + public Double unmarshal(Object o) { + if (o instanceof Double) { + return (Double) o; + } else if (o instanceof Number) { + return ((Number) o).doubleValue(); + } else { + return null; + } + } + + @Override + public Object marshal(Double o) { + return o; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EntityTypeFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EntityTypeFlag.java new file mode 100644 index 000000000..67be8c8cc --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EntityTypeFlag.java @@ -0,0 +1,62 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.entity.EntityTypes; + +import javax.annotation.Nullable; + +/** + * Stores an entity type. + * @deprecated replaced by {@link RegistryFlag}, will be removed in WorldGuard 8 + */ +@Deprecated +public class EntityTypeFlag extends Flag { + + protected EntityTypeFlag(String name, @Nullable RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + protected EntityTypeFlag(String name) { + super(name); + } + + @Override + public EntityType parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + input = input.trim(); + EntityType entityType = unmarshal(input); + if (entityType == null) { + throw new InvalidFlagFormat("Unknown entity type: " + input); + } + return entityType; + } + + @Override + public EntityType unmarshal(@Nullable Object o) { + return EntityTypes.get(String.valueOf(o).toLowerCase()); + } + + @Override + public Object marshal(EntityType o) { + return o.getId(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java new file mode 100644 index 000000000..7c7a4c54a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/EnumFlag.java @@ -0,0 +1,101 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * Stores an enum value. + */ +public class EnumFlag> extends Flag { + + private Class enumClass; + + public EnumFlag(String name, Class enumClass, RegionGroup defaultGroup) { + super(name, defaultGroup); + this.enumClass = enumClass; + } + + public EnumFlag(String name, Class enumClass) { + super(name); + this.enumClass = enumClass; + } + + /** + * Get the enum class. + * + * @return the enum class + */ + public Class getEnumClass() { + return enumClass; + } + + private T findValue(String input) throws IllegalArgumentException { + if (input != null) { + input = input.toUpperCase(); + } + + try { + return Enum.valueOf(enumClass, input); + } catch (IllegalArgumentException e) { + T val = detectValue(input); + + if (val != null) { + return val; + } + + throw e; + } + } + + /** + * Fuzzy detect the value if the value is not found. + * + * @param input string input + * @return value or null + */ + public T detectValue(String input) { + return null; + } + + @Override + public T parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + try { + return findValue(input); + } catch (IllegalArgumentException e) { + throw new InvalidFlagFormat("Unknown value '" + input + "' in " + + enumClass.getName()); + } + } + + @Override + public T unmarshal(Object o) { + try { + return Enum.valueOf(enumClass, String.valueOf(o)); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public Object marshal(T o) { + return o.name(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java new file mode 100644 index 000000000..1cd145b9b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flag.java @@ -0,0 +1,224 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Iterators; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; + +import java.util.Collection; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +/** + * A flag carries extra data on a region. + * + *

Each flag implementation is a singleton and must be registered with a + * {@link FlagRegistry} to be useful. Flag values are stored as their + * proper data type, but they are first "loaded" by calling + * {@link #unmarshal(Object)}. On save, the objects are fed + * through {@link #marshal(Object)} and then saved.

+ */ +public abstract class Flag { + + private static final Pattern VALID_NAME = Pattern.compile("^[:A-Za-z0-9\\-]{1,40}$"); + private final String name; + private final RegionGroupFlag regionGroup; + + /** + * Create a new flag. + * + *

Flag names should match the regex {@code ^[:A-Za-z0-9\-]{1,40}$}.

+ * + * @param name The name of the flag + * @param defaultGroup The default group + * @throws IllegalArgumentException Thrown if the name is invalid + */ + protected Flag(String name, @Nullable RegionGroup defaultGroup) { + if (name != null && !isValidName(name)) { + throw new IllegalArgumentException("Invalid flag name used"); + } + this.name = name; + this.regionGroup = defaultGroup != null ? new RegionGroupFlag(name + "-group", defaultGroup) : null; + } + + /** + * Create a new flag with {@link RegionGroup#ALL} as the default group. + * + *

Flag names should match the regex {@code ^[:A-Za-z0-9\-]{1,40}$}.

+ * + * @param name The name of the flag + * @throws IllegalArgumentException Thrown if the name is invalid + */ + protected Flag(String name) { + this(name, RegionGroup.ALL); + } + + /** + * Get the name of the flag. + * + * @return The name of the flag + */ + public final String getName() { + return name; + } + + /** + * Get the default value. + * + * @return The default value, if one exists, otherwise {@code null} may be returned + */ + @Nullable + public T getDefault() { + return null; + } + + /** + * Given a list of values, choose the best one. + * + *

If there is no "best value" defined, then the first value should + * be returned. The default implementation returns the first value. If + * an implementation does have a strategy defined, then + * {@link #hasConflictStrategy()} should be overridden too.

+ * + * @param values A collection of values + * @return The chosen value + */ + @Nullable + public T chooseValue(Collection values) { + return Iterators.getNext(values.iterator(), null); + } + + /** + * Whether the flag can take a list of values and choose a "best one." + * + *

This is the case with the {@link StateFlag} where {@code DENY} + * overrides {@code ALLOW}, but most flags just return the + * first result from a list.

+ * + *

This flag is primarily used to optimize flag lookup in + * {@link FlagValueCalculator}.

+ * + * @return Whether a best value can be chosen + */ + public boolean hasConflictStrategy() { + return false; + } + + /** + * Whether the flag implicitly has a value set as long as + * {@link Flags#PASSTHROUGH} is not set. + * + *

This value is only changed, at least in WorldGuard, for the + * {@link Flags#BUILD} flag.

+ * + * @return Whether the flag is ignored + */ + public boolean implicitlySetWithMembership() { + return false; + } + + /** + * Whether, if the flag is not set at all, the value should be derived + * from membership. + * + *

This value is only changed, at least in WorldGuard, for the + * {@link Flags#BUILD} flag.

+ * + * @return Whether membership is used + */ + public boolean usesMembershipAsDefault() { + return false; + } + + /** + * Whether the flag requires that a subject is specified in + * {@link FlagValueCalculator}. + * + *

This value is only changed, at least in WorldGuard, for the + * {@link Flags#BUILD} flag.

+ * + * @return Whether a subject is required + */ + public boolean requiresSubject() { + return false; + } + + /** + * Get the region group flag. + * + *

Every group has a region group flag except for region group flags + * themselves.

+ * + * @return The region group flag + */ + public final RegionGroupFlag getRegionGroupFlag() { + return regionGroup; + } + + /** + * Parse a given input to coerce it to a type compatible with the flag. + * + * @param context the {@link FlagContext} + * @return The coerced type + * @throws InvalidFlagFormat Raised if the input is invalid + */ + public abstract T parseInput(FlagContext context) throws InvalidFlagFormat; + + /** + * Convert a raw type that was loaded (from a YAML file, for example) + * into the type that this flag uses. + * + * @param o The object + * @return The unmarshalled type + */ + public abstract T unmarshal(@Nullable Object o); + + /** + * Convert the value stored for this flag into a type that can be + * serialized into YAML. + * + * @param o The object + * @return The marshalled type + */ + public abstract Object marshal(T o); + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "name='" + name + '\'' + + '}'; + } + + /** + * Test whether a flag name is valid. + * + * @param name The flag name + * @return Whether the name is valid + */ + public static boolean isValidName(String name) { + checkNotNull(name, "name"); + return VALID_NAME.matcher(name).matches(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagContext.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagContext.java new file mode 100644 index 000000000..a394a18fb --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagContext.java @@ -0,0 +1,172 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; + +import java.util.Map; + +import javax.annotation.Nullable; + +public final class FlagContext { + + private final Actor sender; + private final String input; + + private Map context; + + private FlagContext(Actor sender, String input, Map values) { + this.sender = sender; + this.input = input; + this.context = values; + } + + public static FlagContextBuilder create() { + return new FlagContextBuilder(); + } + + public void put(String name, Object value) { + context.put(name, value); + } + + public Actor getSender() { + return sender; + } + + public String getUserInput() { + return input; + } + + /** + * Gets the CommandSender as a player. + * + * @return Player + * @throws InvalidFlagFormat if the sender is not a player + */ + public LocalPlayer getPlayerSender() throws InvalidFlagFormat { + if (sender.isPlayer() && sender instanceof LocalPlayer) { + return (LocalPlayer) sender; + } else { + throw new InvalidFlagFormat("Not a player"); + } + } + + public Integer getUserInputAsInt() throws InvalidFlagFormat { + try { + return Integer.parseInt(input); + } catch (NumberFormatException e) { + throw new InvalidFlagFormat("Not a number: " + input); + } + } + + public Double getUserInputAsDouble() throws InvalidFlagFormat { + try { + return Double.parseDouble(input); + } catch (NumberFormatException e) { + throw new InvalidFlagFormat("Not a number: " + input); + } + } + + /** + * Get an object from the context by key name. + * May return null if the object does not exist in the context. + * + * @param name key name of the object + * @return the object matching the key, or null + */ + @Nullable + public Object get(String name) { + return get(name, null); + } + + /** + * Get an object from the context by key name. + * Will only return null if + * a) you provide null as the default + * b) the key has explicity been set to null + * + * @param name key name of the object + * @return the object matching the key + */ + @Nullable + public Object get(String name, Object defaultValue) { + Object obj; + return (((obj = context.get(name)) != null) || context.containsKey(name) + ? obj : defaultValue); + } + + /** + * Create a copy of this FlagContext, with optional substitutions for values + * + * If any supplied variable is null, it will be ignored. + * If a map is supplied, it will override this FlagContext's values of the same key, + * but unprovided keys will not be overriden and will be returned as shallow copies. + * + * @param commandSender CommandSender for the new FlagContext to run under + * @param s String of the user input for the new FlagContext + * @param values map of values to override from the current FlagContext + * @return a copy of this FlagContext + */ + public FlagContext copyWith(@Nullable Actor commandSender, @Nullable String s, @Nullable Map values) { + Map map = Maps.newHashMap(); + map.putAll(context); + if (values != null) { + map.putAll(values); + } + return new FlagContext(commandSender == null ? this.sender : commandSender, s == null ? this.input : s, map); + } + + public static class FlagContextBuilder { + private Actor sender; + private String input; + private Map map = Maps.newHashMap(); + + public FlagContextBuilder setSender(Actor sender) { + this.sender = sender; + return this; + } + + public FlagContextBuilder setInput(String input) { + this.input = input; + return this; + } + + public FlagContextBuilder setObject(String key, Object value) { + this.map.put(key, value); + return this; + } + + public boolean tryAddToMap(String key, Object value) { + if (map.containsKey(key)) return false; + this.map.put(key, value); + return true; + } + + public FlagContext build() { + WorldGuard.getInstance().getPlatform().notifyFlagContextCreate(this); + + return new FlagContext(sender, input, map); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagUtil.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagUtil.java new file mode 100644 index 000000000..c741f2d25 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/FlagUtil.java @@ -0,0 +1,64 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class FlagUtil { + + private static final Logger log = Logger.getLogger(FlagUtil.class.getCanonicalName()); + + private FlagUtil() { + } + + /** + * Marshal a value of flag values into a map of raw values. + * + * @param values The unmarshalled flag values map + * @return The raw values map + */ + public static Map marshal(Map, Object> values) { + checkNotNull(values, "values"); + + Map rawValues = Maps.newHashMap(); + for (Entry, Object> entry : values.entrySet()) { + try { + rawValues.put(entry.getKey().getName(), marshal(entry.getKey(), entry.getValue())); + } catch (Throwable e) { + log.log(Level.WARNING, "Failed to marshal flag value for " + entry.getKey() + "; value is " + entry.getValue(), e); + } + } + + return rawValues; + } + + @SuppressWarnings("unchecked") + private static Object marshal(Flag flag, Object value) { + return flag.marshal((T) value); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flags.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flags.java new file mode 100644 index 000000000..c3edeaec0 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/Flags.java @@ -0,0 +1,278 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.google.common.collect.Sets; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldedit.util.formatting.text.format.TextDecoration; +import com.sk89q.worldedit.util.formatting.text.serializer.legacy.LegacyComponentSerializer; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.flags.registry.FlagConflictException; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * The flags that are used in WorldGuard. + */ +public final class Flags { + + private static final List INBUILT_FLAGS_LIST = new ArrayList<>(); + public static final List INBUILT_FLAGS = Collections.unmodifiableList(INBUILT_FLAGS_LIST); + + // Overrides membership check + public static final StateFlag PASSTHROUGH = register(new StateFlag("passthrough", false)); + public static final SetFlag NONPLAYER_PROTECTION_DOMAINS = register(new SetFlag<>("nonplayer-protection-domains", null, new StringFlag(null))); + + // This flag is unlike the others. It forces the checking of region membership + public static final StateFlag BUILD = register(new BuildFlag("build", true)); + + // These flags are used in tandem with the BUILD flag - if the player can + // build, then the following flags do not need to be checked (although they + // are still checked for DENY), so they are false by default + public static final StateFlag BLOCK_BREAK = register(new StateFlag("block-break", false)); + public static final StateFlag BLOCK_PLACE = register(new StateFlag("block-place", false)); + public static final StateFlag USE = register(new StateFlag("use", false)); + public static final StateFlag INTERACT = register(new StateFlag("interact", false)); + public static final StateFlag DAMAGE_ANIMALS = register(new StateFlag("damage-animals", false)); + public static final StateFlag PVP = register(new StateFlag("pvp", false)); + public static final StateFlag SLEEP = register(new StateFlag("sleep", false)); + public static final StateFlag RESPAWN_ANCHORS = register(new StateFlag("respawn-anchors", false)); + public static final StateFlag TNT = register(new StateFlag("tnt", false)); + public static final StateFlag CHEST_ACCESS = register(new StateFlag("chest-access", false)); + public static final StateFlag PLACE_VEHICLE = register(new StateFlag("vehicle-place", false)); + public static final StateFlag DESTROY_VEHICLE = register(new StateFlag("vehicle-destroy", false)); + public static final StateFlag LIGHTER = register(new StateFlag("lighter", false)); + public static final StateFlag RIDE = register(new StateFlag("ride", false)); + public static final StateFlag POTION_SPLASH = register(new StateFlag("potion-splash", false)); + public static final StateFlag ITEM_FRAME_ROTATE = register(new StateFlag("item-frame-rotation", false)); + public static final StateFlag TRAMPLE_BLOCKS = register(new StateFlag("block-trampling", false)); + public static final StateFlag FIREWORK_DAMAGE = register(new StateFlag("firework-damage", false)); + public static final StateFlag USE_ANVIL = register(new StateFlag("use-anvil", false)); + public static final StateFlag USE_DRIPLEAF = register(new StateFlag("use-dripleaf", false)); + + // These flags are similar to the ones above (used in tandem with BUILD), + // but their defaults are set to TRUE because it is more user friendly. + // However, it is not possible to disable these flags by default in all + // regions because setting DENY in __global__ would also override the + // BUILD flag. In the future, StateFlags will need a DISALLOW state. + public static final StateFlag ITEM_PICKUP = register(new StateFlag("item-pickup", true)); // Intentionally true + public static final StateFlag ITEM_DROP = register(new StateFlag("item-drop", true)); // Intentionally true + public static final StateFlag EXP_DROPS = register(new StateFlag("exp-drops", true)); // Intentionally true + + // These flags adjust behavior and are not checked in tandem with the + // BUILD flag so they need to be TRUE for their defaults. + + // mob griefing related + public static final StateFlag MOB_DAMAGE = register(new StateFlag("mob-damage", true)); + public static final StateFlag CREEPER_EXPLOSION = register(new StateFlag("creeper-explosion", true)); + public static final StateFlag ENDERDRAGON_BLOCK_DAMAGE = register(new StateFlag("enderdragon-block-damage", true)); + public static final StateFlag GHAST_FIREBALL = register(new StateFlag("ghast-fireball", true)); + public static final StateFlag OTHER_EXPLOSION = register(new StateFlag("other-explosion", true)); + public static final StateFlag WITHER_DAMAGE = register(new StateFlag("wither-damage", true)); + public static final StateFlag ENDER_BUILD = register(new StateFlag("enderman-grief", true)); + public static final StateFlag SNOWMAN_TRAILS = register(new StateFlag("snowman-trails", true)); + public static final StateFlag RAVAGER_RAVAGE = register(new StateFlag("ravager-grief", true)); + public static final StateFlag ENTITY_PAINTING_DESTROY = register(new StateFlag("entity-painting-destroy", true)); + public static final StateFlag ENTITY_ITEM_FRAME_DESTROY = register(new StateFlag("entity-item-frame-destroy", true)); + + // mob spawning related + public static final StateFlag MOB_SPAWNING = register(new StateFlag("mob-spawning", true)); + public static final SetFlag DENY_SPAWN = register(new SetFlag<>("deny-spawn", new RegistryFlag<>(null, EntityType.REGISTRY))); + + // block dynamics + public static final StateFlag PISTONS = register(new StateFlag("pistons", true)); + public static final StateFlag FIRE_SPREAD = register(new StateFlag("fire-spread", true)); + public static final StateFlag LAVA_FIRE = register(new StateFlag("lava-fire", true)); + public static final StateFlag LIGHTNING = register(new StateFlag("lightning", true)); + public static final StateFlag SNOW_FALL = register(new StateFlag("snow-fall", true)); + public static final StateFlag SNOW_MELT = register(new StateFlag("snow-melt", true)); + public static final StateFlag ICE_FORM = register(new StateFlag("ice-form", true)); + public static final StateFlag ICE_MELT = register(new StateFlag("ice-melt", true)); + public static final StateFlag FROSTED_ICE_MELT = register(new StateFlag("frosted-ice-melt", true)); + public static final StateFlag FROSTED_ICE_FORM = register(new StateFlag("frosted-ice-form", false)); // this belongs in the first category of "checked with build" + public static final StateFlag MUSHROOMS = register(new StateFlag("mushroom-growth", true)); + public static final StateFlag LEAF_DECAY = register(new StateFlag("leaf-decay", true)); + public static final StateFlag GRASS_SPREAD = register(new StateFlag("grass-growth", true)); + public static final StateFlag MYCELIUM_SPREAD = register(new StateFlag("mycelium-spread", true)); + public static final StateFlag VINE_GROWTH = register(new StateFlag("vine-growth", true)); + public static final StateFlag CROP_GROWTH = register(new StateFlag("crop-growth", true)); + public static final StateFlag SOIL_DRY = register(new StateFlag("soil-dry", true)); + public static final StateFlag CORAL_FADE = register(new StateFlag("coral-fade", true)); + public static final StateFlag WATER_FLOW = register(new StateFlag("water-flow", true)); + public static final StateFlag LAVA_FLOW = register(new StateFlag("lava-flow", true)); + + public static final RegistryFlag WEATHER_LOCK = register(new RegistryFlag<>("weather-lock", WeatherType.REGISTRY)); + public static final StringFlag TIME_LOCK = register(new StringFlag("time-lock")); + + // chat related flags + public static final StateFlag SEND_CHAT = register(new StateFlag("send-chat", true)); + public static final StateFlag RECEIVE_CHAT = register(new StateFlag("receive-chat", true)); + public static final SetFlag BLOCKED_CMDS = register(new SetFlag<>("blocked-cmds", new CommandStringFlag(null))); + public static final SetFlag ALLOWED_CMDS = register(new SetFlag<>("allowed-cmds", new CommandStringFlag(null))); + + // locations + public static final LocationFlag TELE_LOC = register(new LocationFlag("teleport")); + public static final LocationFlag SPAWN_LOC = register(new LocationFlag("spawn", RegionGroup.MEMBERS)); + + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag TELE_MESSAGE = register(new StringFlag("teleport-message", + LegacyComponentSerializer.INSTANCE.serialize(TextComponent.of("").append(TextComponent.of( + "Teleported you to the region '%id%'.", TextColor.LIGHT_PURPLE))))); + + // idk? + public static final StateFlag INVINCIBILITY = register(new StateFlag("invincible", false)); + public static final StateFlag FALL_DAMAGE = register(new StateFlag("fall-damage", true)); + public static final StateFlag HEALTH_REGEN = register(new StateFlag("natural-health-regen", true)); + public static final StateFlag HUNGER_DRAIN = register(new StateFlag("natural-hunger-drain", true)); + + // session and movement based flags + public static final StateFlag ENTRY = register(new StateFlag("entry", true, RegionGroup.NON_MEMBERS)); + public static final StateFlag EXIT = register(new StateFlag("exit", true, RegionGroup.NON_MEMBERS)); + public static final BooleanFlag EXIT_OVERRIDE = register(new BooleanFlag("exit-override")); + public static final StateFlag EXIT_VIA_TELEPORT = register(new StateFlag("exit-via-teleport", true)); + + public static final StateFlag ENDERPEARL = register(new StateFlag("enderpearl", true)); + public static final StateFlag CHORUS_TELEPORT = register(new StateFlag("chorus-fruit-teleport", true)); + + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag GREET_MESSAGE = register(new StringFlag("greeting")); + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag FAREWELL_MESSAGE = register(new StringFlag("farewell")); + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag GREET_TITLE = register(new StringFlag("greeting-title")); + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag FAREWELL_TITLE = register(new StringFlag("farewell-title")); + + public static final BooleanFlag NOTIFY_ENTER = register(new BooleanFlag("notify-enter")); + public static final BooleanFlag NOTIFY_LEAVE = register(new BooleanFlag("notify-leave")); + + public static final RegistryFlag GAME_MODE = register(new RegistryFlag<>("game-mode", GameMode.REGISTRY)); + + private static final Number[] DELAY_VALUES = {0, 1, 5}; + private static final Number[] VITALS_VALUES = {0, 5, 10, 20}; + private static final Number[] VITALS_MINS = {0, 10}; + private static final Number[] VITALS_MAXS = {10, 20}; + + public static final IntegerFlag HEAL_DELAY = register(new IntegerFlag("heal-delay"), f -> f.setSuggestedValues(DELAY_VALUES)); + public static final IntegerFlag HEAL_AMOUNT = register(new IntegerFlag("heal-amount"), f -> f.setSuggestedValues(VITALS_VALUES)); + public static final DoubleFlag MIN_HEAL = register(new DoubleFlag("heal-min-health"), f -> f.setSuggestedValues(VITALS_MINS)); + public static final DoubleFlag MAX_HEAL = register(new DoubleFlag("heal-max-health"), f -> f.setSuggestedValues(VITALS_MAXS)); + + public static final IntegerFlag FEED_DELAY = register(new IntegerFlag("feed-delay"), f -> f.setSuggestedValues(DELAY_VALUES)); + public static final IntegerFlag FEED_AMOUNT = register(new IntegerFlag("feed-amount"), f -> f.setSuggestedValues(VITALS_VALUES)); + public static final IntegerFlag MIN_FOOD = register(new IntegerFlag("feed-min-hunger"), f -> f.setSuggestedValues(VITALS_MINS)); + public static final IntegerFlag MAX_FOOD = register(new IntegerFlag("feed-max-hunger"), f -> f.setSuggestedValues(VITALS_MAXS)); + + // deny messages + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag DENY_MESSAGE = register(new StringFlag("deny-message", + LegacyComponentSerializer.INSTANCE.serialize(TextComponent.of("").append(TextComponent.of("Hey!", + TextColor.RED, Sets.newHashSet(TextDecoration.BOLD))) + .append(TextComponent.of(" Sorry, but you can't %what% here.", TextColor.GRAY))))); + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag ENTRY_DENY_MESSAGE = register(new StringFlag("entry-deny-message", + LegacyComponentSerializer.INSTANCE.serialize(TextComponent.of("").append(TextComponent.of("Hey!", + TextColor.RED, Sets.newHashSet(TextDecoration.BOLD))) + .append(TextComponent.of(" You are not permitted to enter this area.", TextColor.GRAY))))); + /** + * @deprecated The type of this flag will change from a StringFlag to a ComponentFlag to support JSON text + * in a future release. If you depend on the type of this flag, take proper precaution for future breakage. + */ + @Deprecated + public static final StringFlag EXIT_DENY_MESSAGE = register(new StringFlag("exit-deny-message", + LegacyComponentSerializer.INSTANCE.serialize(TextComponent.of("").append(TextComponent.of("Hey!", + TextColor.RED, Sets.newHashSet(TextDecoration.BOLD))) + .append(TextComponent.of(" You are not permitted to leave this area.", TextColor.GRAY))))); + + private Flags() { + } + + private static > T register(final T flag) throws FlagConflictException { + WorldGuard.getInstance().getFlagRegistry().register(flag); + INBUILT_FLAGS_LIST.add(flag.getName()); + return flag; + } + + private static > T register(final T flag, Consumer cfg) throws FlagConflictException { + T f = register(flag); + cfg.accept(f); + return f; + } + + /** + * Try to match the flag with the given ID using a fuzzy name match. + * + * @param flagRegistry the flag registry + * @param id the flag ID + * @return a flag, or null + */ + public static Flag fuzzyMatchFlag(FlagRegistry flagRegistry, String id) { + final String compId = id.replace("-", ""); + for (Flag flag : flagRegistry) { + if (flag.getName().replace("-", "").equalsIgnoreCase(compId)) { + return flag; + } + } + + return null; + } + + /** + * Dummy method to call that initialises the class. + */ + public static void registerAll() { + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/GameModeTypeFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/GameModeTypeFlag.java new file mode 100644 index 000000000..3b8db1e0c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/GameModeTypeFlag.java @@ -0,0 +1,62 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.gamemode.GameModes; + +import javax.annotation.Nullable; + +/** + * Stores an gamemode type. + * @deprecated replaced by {@link RegistryFlag}, will be removed in WorldGuard 8 + */ +@Deprecated +public class GameModeTypeFlag extends Flag { + + protected GameModeTypeFlag(String name, @Nullable RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + protected GameModeTypeFlag(String name) { + super(name); + } + + @Override + public GameMode parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + input = input.trim(); + GameMode gamemode = unmarshal(input); + if (gamemode == null) { + throw new InvalidFlagFormat("Unknown game mode: " + input); + } + return gamemode; + } + + @Override + public GameMode unmarshal(@Nullable Object o) { + return GameModes.get(String.valueOf(o).toLowerCase()); + } + + @Override + public Object marshal(GameMode o) { + return o.getId(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/IntegerFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/IntegerFlag.java new file mode 100644 index 000000000..a9bafe36a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/IntegerFlag.java @@ -0,0 +1,55 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +/** + * Stores an integer. + */ +public class IntegerFlag extends NumberFlag { + + public IntegerFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public IntegerFlag(String name) { + super(name); + } + + @Override + public Integer parseInput(FlagContext context) throws InvalidFlagFormat { + return context.getUserInputAsInt(); + } + + @Override + public Integer unmarshal(Object o) { + if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof Number) { + return ((Number) o).intValue(); + } else { + return null; + } + } + + @Override + public Object marshal(Integer o) { + return o; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/InvalidFlagFormat.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/InvalidFlagFormat.java new file mode 100644 index 000000000..d5d2d46ae --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/InvalidFlagFormat.java @@ -0,0 +1,29 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +public class InvalidFlagFormat extends Exception { + + private static final long serialVersionUID = 8101615074524004172L; + + public InvalidFlagFormat(String msg) { + super(msg); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LazyLocation.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LazyLocation.java new file mode 100644 index 000000000..b39bd41bf --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LazyLocation.java @@ -0,0 +1,101 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.NullWorld; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; + +import javax.annotation.Nullable; +import java.util.Optional; + +/** + * A location that stores the name of the world in case the world is unloaded. + */ +class LazyLocation extends Location { + + private final String worldName; + + @Nullable + private static World findWorld(String worldName) { + return WorldGuard.getInstance().getPlatform().getMatcher().getWorldByName(worldName); + } + + LazyLocation(String worldName, Vector3 position, float yaw, float pitch) { + super(Optional.ofNullable(findWorld(worldName)).orElse(NullWorld.getInstance()), position, yaw, pitch); + this.worldName = worldName; + } + + LazyLocation(String worldName, Vector3 position) { + super(Optional.ofNullable(findWorld(worldName)).orElse(NullWorld.getInstance()), position); + this.worldName = worldName; + } + + @Override + public Extent getExtent() { + if (super.getExtent() != NullWorld.getInstance()) { + return super.getExtent(); + } + // try loading the world again now + // if it fails it will throw an error later (presumably when trying to teleport someone there) + return Optional.ofNullable(findWorld(getWorldName())).orElse(new NullWorld() { + @Override + public String getName() { + return worldName; + } + }); + } + + public String getWorldName() { + return worldName; + } + + public LazyLocation setAngles(float yaw, float pitch) { + return new LazyLocation(worldName, toVector(), yaw, pitch); + } + + @Override + public LazyLocation setPosition(Vector3 position) { + return new LazyLocation(worldName, position, getYaw(), getPitch()); + } + + public LazyLocation add(Vector3 other) { + return this.setPosition(toVector().add(other)); + } + + public LazyLocation add(double x, double y, double z) { + return this.setPosition(toVector().add(x, y, z)); + } + + @Override + public String toString() { + if (getPitch() == 0 && getYaw() == 0) { + return String.join(", ", worldName, + String.valueOf((int) getX()), String.valueOf((int) getY()), String.valueOf((int) getZ())); + } else { + return String.join(", ", worldName, + String.valueOf((int) getX()), String.valueOf((int) getY()), String.valueOf((int) getZ()), + String.valueOf((int) getPitch()), String.valueOf((int) getYaw())); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LocationFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LocationFlag.java new file mode 100644 index 000000000..4f6b6c398 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/LocationFlag.java @@ -0,0 +1,156 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.internal.permission.RegionPermissionModel; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.HashMap; +import java.util.Map; + +public class LocationFlag extends Flag { + + public LocationFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public LocationFlag(String name) { + super(name); + } + + @Override + public Location parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + Player player = context.getPlayerSender(); + + Location loc = null; + if ("here".equalsIgnoreCase(input)) { + Location playerLoc = player.getLocation(); + loc = new LazyLocation(((World) playerLoc.getExtent()).getName(), + playerLoc.toVector(), playerLoc.getYaw(), playerLoc.getPitch()); + } else { + String[] split = input.split(","); + if (split.length >= 3) { + try { + final World world = player.getWorld(); + final double x = Double.parseDouble(split[0]); + final double y = Double.parseDouble(split[1]); + final double z = Double.parseDouble(split[2]); + final float yaw = split.length < 4 ? 0 : Float.parseFloat(split[3]); + final float pitch = split.length < 5 ? 0 : Float.parseFloat(split[4]); + + loc = new LazyLocation(world.getName(), Vector3.at(x, y, z), yaw, pitch); + } catch (NumberFormatException ignored) { + } + } + } + if (loc != null) { + Object obj = context.get("region"); + if (obj instanceof ProtectedRegion) { + ProtectedRegion rg = (ProtectedRegion) obj; + if (WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(player.getWorld()).boundedLocationFlags) { + if (!rg.contains(loc.toVector().toBlockPoint())) { + if (new RegionPermissionModel(player).mayOverrideLocationFlagBounds(rg)) { + player.printDebug("WARNING: Flag location is outside of region."); + } else { + // no permission + throw new InvalidFlagFormat("You can't set that flag outside of the region boundaries."); + } + } + // clamp height to world limits + loc.setPosition(loc.toVector().clampY(0, player.getWorld().getMaxY())); + return loc; + } + } + return loc; + } + throw new InvalidFlagFormat("Expected 'here' or x,y,z."); + } + + @Override + public Location unmarshal(Object o) { + if (o instanceof Map) { + Map map = (Map) o; + + Object rawWorld = map.get("world"); + if (rawWorld == null) return null; + + Object rawX = map.get("x"); + if (rawX == null) return null; + + Object rawY = map.get("y"); + if (rawY == null) return null; + + Object rawZ = map.get("z"); + if (rawZ == null) return null; + + Object rawYaw = map.get("yaw"); + if (rawYaw == null) return null; + + Object rawPitch = map.get("pitch"); + if (rawPitch == null) return null; + + Vector3 position = Vector3.at(toNumber(rawX), toNumber(rawY), toNumber(rawZ)); + float yaw = (float) toNumber(rawYaw); + float pitch = (float) toNumber(rawPitch); + + return new LazyLocation(String.valueOf(rawWorld), position, yaw, pitch); + } + + return null; + } + + @Override + public Object marshal(Location o) { + Vector3 position = o.toVector(); + Map vec = new HashMap<>(); + if (o instanceof LazyLocation) { + vec.put("world", ((LazyLocation) o).getWorldName()); + } else { + try { + if (o.getExtent() instanceof World) { + vec.put("world", ((World) o.getExtent()).getName()); + } + } catch (NullPointerException e) { + return null; + } + } + vec.put("x", position.getX()); + vec.put("y", position.getY()); + vec.put("z", position.getZ()); + vec.put("yaw", o.getYaw()); + vec.put("pitch", o.getPitch()); + return vec; + } + + private double toNumber(Object o) { + if (o instanceof Number) { + return ((Number) o).doubleValue(); + } else { + return 0; + } + + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/MapFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/MapFlag.java new file mode 100644 index 000000000..4472b95ef --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/MapFlag.java @@ -0,0 +1,129 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.google.common.collect.Maps; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Map.Entry; + +import static java.util.Objects.requireNonNull; + +/** + * Stores a key value map of typed {@link Flag}s. + */ +public class MapFlag extends Flag> { + + private final Flag keyFlag; + private final Flag valueFlag; + + public MapFlag(final String name, final Flag keyFlag, final Flag valueFlag) { + super(name); + requireNonNull(keyFlag, "keyFlag cannot be null."); + requireNonNull(valueFlag, "valueFlag cannot be null."); + this.keyFlag = keyFlag; + this.valueFlag = valueFlag; + } + + public MapFlag(final String name, @Nullable final RegionGroup defaultGroup, final Flag keyFlag, final Flag valueFlag) { + super(name, defaultGroup); + requireNonNull(keyFlag, "keyFlag cannot be null."); + requireNonNull(valueFlag, "valueFlag cannot be null."); + this.keyFlag = keyFlag; + this.valueFlag = valueFlag; + } + + /** + * Get the flag that is stored as the key flag type. + * + * @return The key flag type. + */ + public Flag getKeyFlag() { + return this.keyFlag; + } + + /** + * Get the flag type that is stored as values. + * + * @return The value flag type. + */ + public Flag getValueFlag() { + return this.valueFlag; + } + + @Override + public Map parseInput(final FlagContext context) throws InvalidFlagFormat { + + final String input = context.getUserInput(); + if (input.isEmpty()) { + return Maps.newHashMap(); + } + + final Map items = Maps.newHashMap(); + for (final String str : input.split(",")) { + + final char split = str.indexOf('=') == -1 ? ':' : '='; + final String[] keyVal = str.split(String.valueOf(split)); + if (keyVal.length != 2) { + throw new InvalidFlagFormat("Input must be in a 'key:value,key1=value1' format. Either ':' or '=' can be used."); + } + + final FlagContext key = context.copyWith(null, keyVal[0], null); + final FlagContext value = context.copyWith(null, keyVal[1], null); + items.put(this.keyFlag.parseInput(key), this.valueFlag.parseInput(value)); + } + + return items; + } + + @Override + public Map unmarshal(@Nullable final Object o) { + + if (o instanceof Map) { + + final Map map = (Map) o; + final Map items = Maps.newHashMap(); + for (final Entry entry : map.entrySet()) { + + final K keyItem = this.keyFlag.unmarshal(entry.getKey()); + final V valueItem = this.valueFlag.unmarshal(entry.getValue()); + if (keyItem != null && valueItem != null) { + items.put(keyItem, valueItem); + } + } + + return items; + } + + return null; + } + + @Override + public Object marshal(final Map o) { + + final Map map = Maps.newHashMap(); + for (final Entry entry : o.entrySet()) { + map.put(this.keyFlag.marshal(entry.getKey()), this.valueFlag.marshal(entry.getValue())); + } + + return map; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/NumberFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/NumberFlag.java new file mode 100644 index 000000000..9f6764bf9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/NumberFlag.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.annotations.Beta; + +/** + * Stores an Number. + */ +public abstract class NumberFlag extends Flag { + + private static final Number[] EMPTY_NUMBER_ARRAY = new Number[0]; + private Number[] suggestions = EMPTY_NUMBER_ARRAY; + + protected NumberFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + protected NumberFlag(String name) { + super(name); + } + + /** + * Not recommended for public use. Will likely be moved when migrating to piston for commands. + * @param values suggested values + */ + @Beta + public void setSuggestedValues(Number[] values) { + this.suggestions = checkNotNull(values); + } + + /** + * Not recommended for public use. Will likely be moved when migrating to piston for commands. + * @return suggested values + */ + @Beta + public Number[] getSuggestedValues() { + return suggestions; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java new file mode 100644 index 000000000..5a06938ee --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroup.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldguard.domains.Association; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A grouping of region membership types. + */ +public enum RegionGroup { + + MEMBERS(Association.MEMBER, Association.OWNER), + OWNERS(Association.OWNER), + NON_MEMBERS(Association.NON_MEMBER), + NON_OWNERS(Association.MEMBER, Association.NON_MEMBER), + ALL(Association.OWNER, Association.MEMBER, Association.NON_MEMBER), + NONE(); + + private final Set contained; + + RegionGroup(Association... association) { + this.contained = association.length > 0 ? EnumSet.copyOf(Arrays.asList(association)) : EnumSet.noneOf(Association.class); + } + + /** + * Test whether this group contains the given membership status. + * + * @param association membership status + * @return true if contained + */ + public boolean contains(Association association) { + checkNotNull(association); + return contained.contains(association); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java new file mode 100644 index 000000000..0d41f67cc --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegionGroupFlag.java @@ -0,0 +1,96 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; + +/** + * Stores a region group. + */ +public class RegionGroupFlag extends EnumFlag { + + private RegionGroup def; + + public RegionGroupFlag(String name, RegionGroup def) { + super(name, RegionGroup.class, null); + this.def = def; + } + + @Override + public RegionGroup getDefault() { + return def; + } + + @Override + public RegionGroup detectValue(String input) { + input = input.trim(); + + if (input.equalsIgnoreCase("members") || input.equalsIgnoreCase("member")) { + return RegionGroup.MEMBERS; + } else if (input.equalsIgnoreCase("owners") || input.equalsIgnoreCase("owner")) { + return RegionGroup.OWNERS; + } else if (input.equalsIgnoreCase("nonowners") || input.equalsIgnoreCase("nonowner")) { + return RegionGroup.NON_OWNERS; + } else if (input.equalsIgnoreCase("nonmembers") || input.equalsIgnoreCase("nonmember")) { + return RegionGroup.NON_MEMBERS; + } else if (input.equalsIgnoreCase("everyone") || input.equalsIgnoreCase("anyone") || input.equalsIgnoreCase("all")) { + return RegionGroup.ALL; + } else if (input.equalsIgnoreCase("none") || input.equalsIgnoreCase("noone") || input.equalsIgnoreCase("deny")) { + return RegionGroup.NONE; + } else { + return null; + } + } + + public static boolean isMember(ProtectedRegion region, RegionGroup group, @Nullable LocalPlayer player) { + if (group == null || group == RegionGroup.ALL) { + return true; + } else if (group == RegionGroup.OWNERS) { + return player != null && region.isOwner(player); + } else if (group == RegionGroup.MEMBERS) { + return player != null && region.isMember(player); + } else if (group == RegionGroup.NON_OWNERS) { + return player == null || !region.isOwner(player); + } else if (group == RegionGroup.NON_MEMBERS) { + return player == null || !region.isMember(player); + } + return false; + } + + public static boolean isMember(ApplicableRegionSet set, @Nullable RegionGroup group, LocalPlayer player) { + if (group == null || group == RegionGroup.ALL) { + return true; + } else if (group == RegionGroup.OWNERS) { + return set.isOwnerOfAll(player); + } else if (group == RegionGroup.MEMBERS) { + return set.isMemberOfAll(player); + } else if (group == RegionGroup.NON_OWNERS) { + return !set.isOwnerOfAll(player); + } else if (group == RegionGroup.NON_MEMBERS) { + return !set.isMemberOfAll(player); + } + return false; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegistryFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegistryFlag.java new file mode 100644 index 000000000..cb7bf057d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/RegistryFlag.java @@ -0,0 +1,66 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.Registry; + +import javax.annotation.Nullable; +import java.util.Locale; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class RegistryFlag extends Flag { + private final Registry registry; + + public RegistryFlag(String name, Registry registry) { + super(name); + requireNonNull(registry, "registry cannot be null."); + this.registry = registry; + } + + public RegistryFlag(String name, @Nullable RegionGroup defaultGroup, Registry registry) { + super(name, defaultGroup); + requireNonNull(registry, "registry cannot be null."); + this.registry = registry; + } + + @Override + public T parseInput(FlagContext context) throws InvalidFlagFormat { + final String key = context.getUserInput().trim().toLowerCase(Locale.ROOT); + return Optional.ofNullable(registry.get(key)) + .orElseThrow(() -> new InvalidFlagFormat("Unknown " + registry.getName() + ": " + key)); + } + + public Registry getRegistry() { + return registry; + } + + @Override + public T unmarshal(@Nullable Object o) { + return registry.get(String.valueOf(o).toLowerCase(Locale.ROOT)); + } + + @Override + public Object marshal(T o) { + return o.getId(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java new file mode 100644 index 000000000..e850e3955 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/SetFlag.java @@ -0,0 +1,106 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.google.common.collect.Sets; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +/** + * Stores a set of types. + */ +public class SetFlag extends Flag> { + + private Flag subFlag; + + public SetFlag(String name, RegionGroup defaultGroup, Flag subFlag) { + super(name, defaultGroup); + requireNonNull(subFlag, "SubFlag cannot be null."); + this.subFlag = subFlag; + } + + public SetFlag(String name, Flag subFlag) { + super(name); + requireNonNull(subFlag, "SubFlag cannot be null."); + this.subFlag = subFlag; + } + + /** + * Get the flag that is stored in this flag. + * + * @return the stored flag type + */ + public Flag getType() { + return subFlag; + } + + @Override + public Set parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + if (input.isEmpty()) { + return Sets.newHashSet(); + } else { + Set items = Sets.newHashSet(); + + for (String str : input.split(",")) { + FlagContext copy = context.copyWith(null, str, null); + items.add(subFlag.parseInput(copy)); + } + + return items; + } + } + + @Override + public Set unmarshal(Object o) { + if (o instanceof Collection) { + Collection collection = (Collection) o; + Set items = new HashSet<>(); + + for (Object sub : collection) { + T item = subFlag.unmarshal(sub); + if (item != null) { + items.add(item); + } + } + + return items; + } else { + return null; + } + } + + @Override + public Object marshal(Set o) { + List list = new ArrayList<>(); + for (T item : o) { + list.add(subFlag.marshal(item)); + } + + return list; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StateFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StateFlag.java new file mode 100644 index 000000000..8e21589d4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StateFlag.java @@ -0,0 +1,206 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import javax.annotation.Nullable; +import java.util.Collection; + +/** + * Stores a bi-state value. + */ +public class StateFlag extends Flag { + + public enum State { + ALLOW, + DENY + } + + private boolean def; + + public StateFlag(String name, boolean def, RegionGroup defaultGroup) { + super(name, defaultGroup); + this.def = def; + } + + public StateFlag(String name, boolean def) { + super(name); + this.def = def; + } + + @Override + public State getDefault() { + return def ? State.ALLOW : null; + } + + @Override + public boolean hasConflictStrategy() { + return true; + } + + @Override + @Nullable + public State chooseValue(Collection values) { + if (!values.isEmpty()) { + return combine(values); + } else { + return null; + } + } + + /** + * Whether setting this flag to {@link State#ALLOW} is prevented on + * the global region. + * + *

This value is only changed, at least in WorldGuard, for the + * {@link Flags#BUILD} flag.

+ * + * @return Whether {@code ALLOW} is prevented + */ + public boolean preventsAllowOnGlobal() { + return false; + } + + @Override + public State parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + + if (input.equalsIgnoreCase("allow")) { + return State.ALLOW; + } else if (input.equalsIgnoreCase("deny")) { + return State.DENY; + } else if (input.equalsIgnoreCase("none")) { + return null; + } else { + throw new InvalidFlagFormat("Expected none/allow/deny but got '" + input + "'"); + } + } + + @Override + public State unmarshal(Object o) { + String str = o.toString(); + if (str.equalsIgnoreCase("allow")) { + return State.ALLOW; + } else if (str.equalsIgnoreCase("deny")) { + return State.DENY; + } else { + return null; + } + } + + @Override + public Object marshal(State o) { + if (o == State.ALLOW) { + return "allow"; + } else if (o == State.DENY) { + return "deny"; + } else { + return null; + } + } + + /** + * Test whether at least one of the given states is {@code ALLOW} + * but none are set to {@code DENY}. + * + * @param states zero or more states + * @return true if the condition is matched + */ + public static boolean test(State... states) { + boolean allowed = false; + + for (State state : states) { + if (state == State.DENY) { + return false; + } else if (state == State.ALLOW) { + allowed = true; + } + } + + return allowed; + } + + /** + * Combine states, letting {@code DENY} override {@code ALLOW} and + * {@code ALLOW} override {@code NONE} (or null). + * + * @param states zero or more states + * @return the new state + */ + @Nullable + public static State combine(State... states) { + boolean allowed = false; + + for (State state : states) { + if (state == State.DENY) { + return State.DENY; + } else if (state == State.ALLOW) { + allowed = true; + } + } + + return allowed ? State.ALLOW : null; + } + + /** + * Combine states, letting {@code DENY} override {@code ALLOW} and + * {@code ALLOW} override {@code NONE} (or null). + * + * @param states zero or more states + * @return the new state + */ + @Nullable + public static State combine(Collection states) { + boolean allowed = false; + + for (State state : states) { + if (state == State.DENY) { + return State.DENY; + } else if (state == State.ALLOW) { + allowed = true; + } + } + + return allowed ? State.ALLOW : null; + } + + /** + * Turn a boolean into either {@code NONE} (null) or {@code ALLOW} if + * the boolean is false or true, respectively. + * + * @param flag a boolean value + * @return a state + */ + @Nullable + public static State allowOrNone(boolean flag) { + return flag ? State.ALLOW : null; + } + + /** + * Turn {@code DENY} into {@code NONE} (null). + * + * @param state a state + * @return a state + */ + @Nullable + public static State denyToNone(State state) { + return state == State.DENY ? null : state; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StringFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StringFlag.java new file mode 100644 index 000000000..080e2571a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/StringFlag.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldguard.commands.CommandUtils; + +import javax.annotation.Nullable; + +/** + * Stores a string. + */ +public class StringFlag extends Flag { + + private final String defaultValue; + + public StringFlag(String name) { + super(name); + this.defaultValue = null; + } + + public StringFlag(String name, String defaultValue) { + super(name); + this.defaultValue = defaultValue; + } + + public StringFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + this.defaultValue = null; + } + + public StringFlag(String name, RegionGroup defaultGroup, String defaultValue) { + super(name, defaultGroup); + this.defaultValue = defaultValue; + } + + @Nullable + @Override + public String getDefault() { + return defaultValue; + } + + @Override + public String parseInput(FlagContext context) throws InvalidFlagFormat { + String lines = context.getUserInput().replaceAll("(? + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import javax.annotation.Nullable; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; + +/** + * Stores a timestamp. + */ +public class TimestampFlag extends Flag { + private static final DateTimeFormatter SERIALIZER = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneOffset.UTC); + private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart().appendOffsetId() + .toFormatter(); + + public TimestampFlag(String name, @Nullable RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public TimestampFlag(String name) { + super(name); + } + + @Override + public Instant parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + if ("now".equalsIgnoreCase(input)) { + return Instant.now(); + } else { + try { + TemporalAccessor parsed = PARSER.parseBest(input, ZonedDateTime::from, LocalDateTime::from); + // convert whatever input into UTC for storage + if (parsed instanceof LocalDateTime) { + return ((LocalDateTime) parsed).atZone(ZoneOffset.UTC).toInstant(); + } else if (parsed instanceof ZonedDateTime) { + return ((ZonedDateTime) parsed).toInstant(); + } else { + throw new InvalidFlagFormat("Unrecognized input."); + } + } catch (DateTimeParseException ignored) { + throw new InvalidFlagFormat("Expected 'now' or ISO 8601 formatted input."); + } + } + } + + @Override + public Instant unmarshal(@Nullable Object o) { + if (o instanceof String) { + try { + return Instant.from(SERIALIZER.parse((String) o)); + } catch(DateTimeParseException ignored) { + return null; + } + } + return null; + } + + @Override + public Object marshal(Instant o) { + return SERIALIZER.format(o); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/UUIDFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/UUIDFlag.java new file mode 100644 index 000000000..40d20a99e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/UUIDFlag.java @@ -0,0 +1,64 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import javax.annotation.Nullable; +import java.util.UUID; + +public class UUIDFlag extends Flag { + + public UUIDFlag(String name, @Nullable RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public UUIDFlag(String name) { + super(name); + } + + @Override + public UUID parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + if ("self".equalsIgnoreCase(input)) { + return context.getSender().getUniqueId(); + } + try { + return UUID.fromString(input); + } catch (IllegalArgumentException e) { + throw new InvalidFlagFormat("Not a valid uuid: " + input); + } + } + + @Override + public UUID unmarshal(@Nullable Object o) { + if (!(o instanceof String)) { + return null; + } + try { + return UUID.fromString((String)o); + } catch (IllegalArgumentException e) { + return null; + } + } + + @Override + public Object marshal(UUID o) { + return o.toString(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/VectorFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/VectorFlag.java new file mode 100644 index 000000000..80683f093 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/VectorFlag.java @@ -0,0 +1,104 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.math.Vector3; + +import java.util.HashMap; +import java.util.Map; + +/** + * Stores a vector. + */ +public class VectorFlag extends Flag { + + public VectorFlag(String name, RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + public VectorFlag(String name) { + super(name); + } + + @Override + public Vector3 parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + + if ("here".equalsIgnoreCase(input)) { + return context.getPlayerSender().getLocation().toVector(); + } else { + String[] split = input.split(","); + if (split.length == 3) { + try { + return Vector3.at( + Double.parseDouble(split[0]), + Double.parseDouble(split[1]), + Double.parseDouble(split[2]) + ); + } catch (NumberFormatException ignored) { + } + } + + throw new InvalidFlagFormat("Expected 'here' or x,y,z."); + } + } + + @Override + public Vector3 unmarshal(Object o) { + if (o instanceof Map) { + Map map = (Map) o; + + Object rawX = map.get("x"); + Object rawY = map.get("y"); + Object rawZ = map.get("z"); + + if (rawX == null || rawY == null || rawZ == null) { + return null; + } + + return Vector3.at(toNumber(rawX), toNumber(rawY), toNumber(rawZ)); + } + + return null; + } + + @Override + public Object marshal(Vector3 o) { + Map vec = new HashMap<>(); + vec.put("x", o.getX()); + vec.put("y", o.getY()); + vec.put("z", o.getZ()); + return vec; + } + + private double toNumber(Object o) { + if (o instanceof Integer) { + return (Integer) o; + } else if (o instanceof Long) { + return (Long) o; + } else if (o instanceof Float) { + return (Float) o; + } else if (o instanceof Double) { + return (Double) o; + } else { + return 0; + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/WeatherTypeFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/WeatherTypeFlag.java new file mode 100644 index 000000000..c103baf1a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/WeatherTypeFlag.java @@ -0,0 +1,62 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags; + +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldedit.world.weather.WeatherTypes; + +import javax.annotation.Nullable; + +/** + * Stores an weather type. + * @deprecated replaced by {@link RegistryFlag}, will be removed in WorldGuard 8 + */ +@Deprecated +public class WeatherTypeFlag extends Flag { + + protected WeatherTypeFlag(String name, @Nullable RegionGroup defaultGroup) { + super(name, defaultGroup); + } + + protected WeatherTypeFlag(String name) { + super(name); + } + + @Override + public WeatherType parseInput(FlagContext context) throws InvalidFlagFormat { + String input = context.getUserInput(); + input = input.trim(); + WeatherType weatherType = unmarshal(input); + if (weatherType == null) { + throw new InvalidFlagFormat("Unknown weather type: " + input); + } + return weatherType; + } + + @Override + public WeatherType unmarshal(@Nullable Object o) { + return WeatherTypes.get(String.valueOf(o).toLowerCase()); + } + + @Override + public Object marshal(WeatherType o) { + return o.getId(); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagConflictException.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagConflictException.java new file mode 100644 index 000000000..f87dab7fa --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagConflictException.java @@ -0,0 +1,28 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags.registry; + +public class FlagConflictException extends RuntimeException { + + public FlagConflictException(String message) { + super(message); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagRegistry.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagRegistry.java new file mode 100644 index 000000000..17ace18af --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/FlagRegistry.java @@ -0,0 +1,93 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags.registry; + +import com.sk89q.worldguard.protection.flags.Flag; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * Keeps track of registered flags. + */ +public interface FlagRegistry extends Iterable> { + + /** + * Register a new flag. + * + *

There may be an appropriate time to register flags. If flags are + * registered outside this time, then an exception may be thrown.

+ * + * @param flag The flag + * @throws FlagConflictException Thrown when an existing flag exists with the same name + * @throws IllegalStateException If it is not the right time to register new flags + */ + void register(Flag flag) throws FlagConflictException; + + /** + * Register a collection of flags. + * + *

There may be an appropriate time to register flags. If flags are + * registered outside this time, then an exception may be thrown.

+ * + *

If there is a flag conflict, then an error will be logged but + * no exception will be thrown.

+ * + * @param flags a collection of flags + * @throws IllegalStateException If it is not the right time to register new flags + */ + void registerAll(Collection> flags); + + /** + * Get af flag by its name. + * + * @param name The name + * @return The flag, if it has been registered + */ + @Nullable + Flag get(String name); + + /** + * Get all flags + * + * @return All flags + */ + List> getAll(); + + /** + * Unmarshal a raw map of values into a map of flags with their + * unmarshalled values. + * + * @param rawValues The raw values map + * @param createUnknown Whether "just in time" flags should be created for unknown flags + * @return The unmarshalled flag values map + */ + Map, Object> unmarshal(Map rawValues, boolean createUnknown); + + /** + * Get the number of registered flags. + * + * @return The number of registered flags + */ + int size(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/SimpleFlagRegistry.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/SimpleFlagRegistry.java new file mode 100644 index 000000000..741d17491 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/SimpleFlagRegistry.java @@ -0,0 +1,181 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags.registry; + +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class SimpleFlagRegistry implements FlagRegistry { + + private static final Logger log = Logger.getLogger(SimpleFlagRegistry.class.getCanonicalName()); + + private final Object lock = new Object(); + private final ConcurrentMap> flags = Maps.newConcurrentMap(); + private boolean initialized = false; + + public boolean isInitialized() { + return initialized; + } + + public void setInitialized(boolean initialized) { + this.initialized = initialized; + } + + @Override + public void register(Flag flag) throws FlagConflictException { + synchronized (lock) { + if (initialized) { + throw new IllegalStateException("New flags cannot be registered at this time"); + } + + forceRegister(flag); + } + } + + @Override + public void registerAll(Collection> flags) { + synchronized (lock) { + for (Flag flag : flags) { + try { + register(flag); + } catch (FlagConflictException e) { + log.log(Level.WARNING, e.getMessage()); + } + } + } + } + + private Flag forceRegister(Flag flag) throws FlagConflictException { + checkNotNull(flag, "flag"); + checkNotNull(flag.getName(), "flag.getName()"); + + synchronized (lock) { + String name = flag.getName().toLowerCase(); + if (flags.containsKey(name)) { + throw new FlagConflictException("A flag already exists by the name " + name); + } + + flags.put(name, flag); + } + + return flag; + } + + @Override + @Nullable + public Flag get(String name) { + checkNotNull(name, "name"); + return flags.get(name.toLowerCase()); + } + + @Override + public List> getAll() { + return Lists.newArrayList(this.flags.values()); + } + + private Flag getOrCreate(String name) { + Flag flag = get(name); + + if (flag != null) { + return flag; + } + + synchronized (lock) { + flag = get(name); // Load again because the previous load was not synchronized + return flag != null ? flag : forceRegister(new UnknownFlag(name)); + } + } + + @Override + public Map, Object> unmarshal(Map rawValues, boolean createUnknown) { + checkNotNull(rawValues, "rawValues"); + + // Ensure that flags are registered. + Flags.registerAll(); + + ConcurrentMap, Object> values = Maps.newConcurrentMap(); + ConcurrentMap regionFlags = Maps.newConcurrentMap(); + + for (Entry entry : rawValues.entrySet()) { + if (entry.getKey().endsWith("-group")) { + regionFlags.put(entry.getKey(), entry.getValue()); + continue; + } + Flag flag = createUnknown ? getOrCreate(entry.getKey()) : get(entry.getKey()); + + if (flag != null) { + try { + Object unmarshalled = flag.unmarshal(entry.getValue()); + + if (unmarshalled != null) { + values.put(flag, unmarshalled); + } else { + log.warning("Failed to parse flag '" + flag.getName() + "' with value '" + entry.getValue() + "'"); + } + } catch (Throwable e) { + log.log(Level.WARNING, "Failed to unmarshal flag value for " + flag, e); + } + } + } + for (Entry entry : regionFlags.entrySet()) { + String parentName = entry.getKey().replaceAll("-group", ""); + Flag parent = get(parentName); + if (parent == null || parent instanceof UnknownFlag) { + if (createUnknown && get(entry.getKey()) == null) { + final UnknownFlag unknownFlag = new UnknownFlag(entry.getKey()); + forceRegister(unknownFlag); + } + Flag unk = get(entry.getKey()); + if (unk != null) { + values.put(unk, entry.getValue()); + } + } else { + values.put(parent.getRegionGroupFlag(), parent.getRegionGroupFlag().unmarshal(entry.getValue())); + } + } + + return values; + } + + @Override + public int size() { + return flags.size(); + } + + @Override + public Iterator> iterator() { + return Iterators.unmodifiableIterator(flags.values().iterator()); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/UnknownFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/UnknownFlag.java new file mode 100644 index 000000000..29996043e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/flags/registry/UnknownFlag.java @@ -0,0 +1,49 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.flags.registry; + +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.FlagContext; +import com.sk89q.worldguard.protection.flags.InvalidFlagFormat; + +import javax.annotation.Nullable; + +public class UnknownFlag extends Flag { + + public UnknownFlag(String name) { + super(name); + } + + @Override + public Object parseInput(FlagContext context) throws InvalidFlagFormat { + throw new InvalidFlagFormat("The plugin that registered this flag is not currently installed"); + } + + @Override + public Object unmarshal(@Nullable Object o) { + return o; + } + + @Override + public Object marshal(Object o) { + return o; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionContainerImpl.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionContainerImpl.java new file mode 100644 index 000000000..8418cddde --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionContainerImpl.java @@ -0,0 +1,298 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.index.ChunkHashTable; +import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex; +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.util.Normal; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages different {@link RegionManager}s for different worlds or dimensions. + * + *

This is an internal class. Do not use it.

+ */ +public class RegionContainerImpl { + + private static final Logger log = Logger.getLogger(RegionContainerImpl.class.getCanonicalName()); + private static final int LOAD_ATTEMPT_INTERVAL = 1000 * 30; + private static final int SAVE_INTERVAL = 1000 * 30; + + private final ConcurrentMap mapping = new ConcurrentHashMap<>(); + private final Object lock = new Object(); + private final RegionDriver driver; + private final Function indexFactory = new ChunkHashTable.Factory(new PriorityRTreeIndex.Factory()); + private final Timer timer = new Timer("WorldGuard Region I/O"); + private final FlagRegistry flagRegistry; + + private final Set failingLoads = new HashSet<>(); + private final Set failingSaves = Collections.synchronizedSet( + Collections.newSetFromMap(new WeakHashMap<>())); + + /** + * Create a new instance. + * + * @param driver the region store driver + * @param flagRegistry the flag registry + */ + public RegionContainerImpl(RegionDriver driver, FlagRegistry flagRegistry) { + checkNotNull(driver); + checkNotNull(flagRegistry, "flagRegistry"); + this.driver = driver; + timer.schedule(new BackgroundLoader(), LOAD_ATTEMPT_INTERVAL, LOAD_ATTEMPT_INTERVAL); + timer.schedule(new BackgroundSaver(), SAVE_INTERVAL, SAVE_INTERVAL); + this.flagRegistry = flagRegistry; + } + + /** + * Get the region store driver. + * + * @return the driver + */ + public RegionDriver getDriver() { + return driver; + } + + /** + * Load the {@code RegionManager} for the world with the given name, + * creating a new instance for the world if one does not exist yet. + * + * @param name the name of the world + * @return a region manager, or {@code null} if loading failed + */ + @Nullable + public RegionManager load(String name) { + checkNotNull(name); + + Normal normal = Normal.normal(name); + + synchronized (lock) { + RegionManager manager = mapping.get(normal); + if (manager != null) { + return manager; + } else { + try { + manager = createAndLoad(name); + mapping.put(normal, manager); + failingLoads.remove(normal); + return manager; + } catch (StorageException e) { + log.log(Level.WARNING, "Failed to load the region data for '" + name + "' (periodic attempts will be made to load the data until success)", e); + failingLoads.add(normal); + return null; + } + } + } + } + + /** + * Create a new region manager and load the data. + * + * @param name the name of the world + * @return a region manager + * @throws StorageException thrown if loading fals + */ + private RegionManager createAndLoad(String name) throws StorageException { + RegionDatabase store = driver.get(name); + RegionManager manager = new RegionManager(store, indexFactory, flagRegistry); + manager.load(); // Try loading, although it may fail + return manager; + } + + /** + * Unload the region manager associated with the given world name. + * + *

If no region manager has been loaded for the given name, then + * nothing will happen.

+ * + * @param name the name of the world + */ + public void unload(String name) { + checkNotNull(name); + + Normal normal = Normal.normal(name); + + synchronized (lock) { + RegionManager manager = mapping.get(normal); + if (manager != null) { + try { + manager.save(); + } catch (StorageException e) { + log.log(Level.WARNING, "Failed to save the region data for '" + name + "'", e); + } + + mapping.remove(normal); + failingSaves.remove(manager); + } + + failingLoads.remove(normal); + } + } + + /** + * Unload all region managers and save their contents before returning. + * This message may block for an extended period of time. + */ + public void unloadAll() { + synchronized (lock) { + for (Map.Entry entry : mapping.entrySet()) { + String name = entry.getKey().toString(); + RegionManager manager = entry.getValue(); + try { + manager.saveChanges(); + } catch (StorageException e) { + log.log(Level.WARNING, "Failed to save the region data for '" + name + "' while unloading the data for all worlds", e); + } + } + + mapping.clear(); + failingLoads.clear(); + failingSaves.clear(); + } + } + + /** + * Disable completely. + */ + public void shutdown() { + timer.cancel(); + unloadAll(); + } + + /** + * Get the region manager for the given world name. + * + * @param name the name of the world + * @return a region manager, or {@code null} if one was never loaded + */ + @Nullable + public RegionManager get(String name) { + checkNotNull(name); + return mapping.get(Normal.normal(name)); + } + + /** + * Get an immutable list of loaded region managers. + * + * @return an immutable list + */ + public List getLoaded() { + return Collections.unmodifiableList(new ArrayList<>(mapping.values())); + } + + /** + * Get the a set of region managers that are failing to save. + * + * @return a set of region managers + */ + public Set getSaveFailures() { + return new HashSet<>(failingSaves); + } + + /** + * A task to save managers in the background. + */ + private class BackgroundSaver extends TimerTask { + @Override + public void run() { + synchronized (lock) { + // Block loading of new region managers + + for (Map.Entry entry : mapping.entrySet()) { + String name = entry.getKey().toString(); + RegionManager manager = entry.getValue(); + try { + if (manager.saveChanges()) { + log.info("Region data changes made in '" + name + "' have been background saved"); + } + failingSaves.remove(manager); + } catch (StorageException e) { + failingSaves.add(manager); + log.log(Level.WARNING, "Failed to save the region data for '" + name + "' during a periodical save", e); + } catch (Exception e) { + failingSaves.add(manager); + log.log(Level.WARNING, "An expected error occurred during a periodical save", e); + } + } + } + } + } + + /** + * A task to re-try loading region data that has not yet been + * successfully loaded. + */ + private class BackgroundLoader extends TimerTask { + private String lastMsg; + + @Override + public void run() { + synchronized (lock) { + if (!failingLoads.isEmpty()) { + log.info("Attempting to load region data that has previously failed to load..."); + + Iterator it = failingLoads.iterator(); + while (it.hasNext()) { + Normal normal = it.next(); + try { + RegionManager manager = createAndLoad(normal.toString()); + mapping.put(normal, manager); + it.remove(); + log.info("Successfully loaded region data for '" + normal.toString() + "'"); + } catch (StorageException e) { + if (e.getCause() != null && e.getCause().getMessage().equals(lastMsg)) { + // if it's the same error, don't print a whole stacktrace + log.log(Level.WARNING, "Region data is still failing to load, at least for the world named '" + normal.toString() + "'"); + break; + } + log.log(Level.WARNING, "Region data is still failing to load, at least for the world named '" + normal.toString() + "'", e); + lastMsg = e.getCause() == null ? e.getMessage() : e.getCause().getMessage(); + break; + } + } + } + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java new file mode 100644 index 000000000..29411f27a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionDifference.java @@ -0,0 +1,83 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Collections; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes the difference in region data. + */ +public final class RegionDifference { + + private final Set changed; + private final Set removed; + + /** + * Create a new instance. + * + * @param changed a set of regions that were changed or added + * @param removed a set of regions that were removed + */ + public RegionDifference(Set changed, Set removed) { + checkNotNull(changed); + checkNotNull(removed); + + this.changed = changed; + this.removed = removed; + } + + /** + * Get the regions that were changed or added. + * + * @return regions + */ + public Set getChanged() { + return Collections.unmodifiableSet(changed); + } + + /** + * Get the regions that were removed. + * + * @return regions + */ + public Set getRemoved() { + return Collections.unmodifiableSet(removed); + } + + /** + * Test whether there are changes or removals. + * + * @return true if there are changes + */ + public boolean containsChanges() { + return !changed.isEmpty() || !removed.isEmpty(); + } + + public void addAll(RegionDifference diff) { + changed.addAll(diff.getChanged()); + removed.addAll(diff.getRemoved()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java new file mode 100644 index 000000000..2f22bdd42 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RegionManager.java @@ -0,0 +1,441 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Sets; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.RegionResultSet; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.index.ConcurrentRegionIndex; +import com.sk89q.worldguard.protection.managers.index.RegionIndex; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption; +import com.sk89q.worldguard.util.Normal; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +/** + * A region manager holds the regions for a world. + */ +public final class RegionManager { + + private final RegionDatabase store; + private final Function indexFactory; + private final FlagRegistry flagRegistry; + private ConcurrentRegionIndex index; + + /** + * Create a new index. + * + * @param store the region store + * @param indexFactory the factory for creating new instances of the index + * @param flagRegistry the flag registry + */ + public RegionManager(RegionDatabase store, Function indexFactory, FlagRegistry flagRegistry) { + checkNotNull(store); + checkNotNull(indexFactory); + checkNotNull(flagRegistry, "flagRegistry"); + + this.store = store; + this.indexFactory = indexFactory; + this.index = indexFactory.apply(store.getName()); + this.flagRegistry = flagRegistry; + } + + /** + * Get a displayable name for this store. + */ + public String getName() { + return store.getName(); + } + + /** + * Load regions from storage and replace the index on this manager with + * the regions loaded from the store. + * + *

This method will block until the save completes, but it will + * not block access to the region data from other threads, nor will it + * prevent the creation or modification of regions in the index while + * a new collection of regions is loaded from storage.

+ * + * @throws StorageException thrown when loading fails + */ + public void load() throws StorageException { + Set regions = store.loadAll(flagRegistry); + for (ProtectedRegion region : regions) { + region.setDirty(false); + } + setRegions(regions); + } + + /** + * Save a snapshot of all the regions as it is right now to storage. + * + * @throws StorageException thrown on save error + */ + public void save() throws StorageException { + index.setDirty(false); + store.saveAll(new HashSet<>(getFilteredValuesCopy())); + } + + /** + * Save changes to the region index to disk, preferring to only save + * the changes (rather than the whole index), but choosing to save the + * whole index if the underlying store does not support partial saves. + * + *

This method does nothing if there are no changes.

+ * + * @return true if there were changes to be saved + * @throws StorageException thrown on save error + */ + public boolean saveChanges() throws StorageException { + RegionDifference diff = index.getAndClearDifference(); + boolean successful = false; + + try { + if (diff.containsChanges()) { + try { + store.saveChanges(diff); + } catch (DifferenceSaveException e) { + save(); // Partial save is not supported + } + successful = true; + return true; + } else { + successful = true; + return false; + } + } finally { + if (!successful) { + index.setDirty(diff); + } + } + } + + /** + * Load the regions for a chunk. + * + * @param position the position + */ + public void loadChunk(BlockVector2 position) { + index.bias(position); + } + + /** + * Load the regions for a chunk. + * + * @param positions a collection of positions + */ + public void loadChunks(Collection positions) { + index.biasAll(positions); + } + + /** + * Unload the regions for a chunk. + * + * @param position the position + */ + public void unloadChunk(BlockVector2 position) { + index.forget(position); + } + + /** + * Get an unmodifiable map of regions containing the state of the + * index at the time of call. + * + *

This call is relatively heavy (and may block other threads), + * so refrain from calling it frequently.

+ * + * @return a map of regions + */ + public Map getRegions() { + Map map = new HashMap<>(); + for (ProtectedRegion region : index.values()) { + map.put(Normal.normalize(region.getId()), region); + } + return Collections.unmodifiableMap(map); + } + + /** + * Replace the index with the regions in the given map. + * + *

The parents of the regions will also be added to the index, even + * if they are not in the provided map.

+ * + * @param regions a map of regions + */ + public void setRegions(Map regions) { + checkNotNull(regions); + + setRegions(regions.values()); + } + + /** + * Replace the index with the regions in the given collection. + * + *

The parents of the regions will also be added to the index, even + * if they are not in the provided map.

+ * + * @param regions a collection of regions + */ + public void setRegions(Collection regions) { + checkNotNull(regions); + + ConcurrentRegionIndex newIndex = indexFactory.apply(getName()); + newIndex.addAll(regions); + newIndex.getAndClearDifference(); // Clear changes + this.index = newIndex; + } + + /** + * Aad a region to the manager. + * + *

The parents of the region will also be added to the index.

+ * + * @param region the region + */ + public void addRegion(ProtectedRegion region) { + checkNotNull(region); + index.add(region); + } + + /** + * Return whether the index contains a region by the given name, + * with equality determined by {@link Normal}. + * + * @param id the name of the region + * @return true if this index contains the region + */ + public boolean hasRegion(String id) { + return index.contains(id); + } + + /** + * Get the region named by the given name (equality determined using + * {@link Normal}). + * + * @param id the name of the region + * @return a region or {@code null} + */ + @Nullable + public ProtectedRegion getRegion(String id) { + checkNotNull(id); + return index.get(id); + } + + /** + * @deprecated Use exact ids with {@link #getRegion} + */ + @Nullable + @Deprecated + public ProtectedRegion matchRegion(String pattern) { + return getRegion(pattern); + } + + /** + * Remove a region from the index with the given name, opting to remove + * the children of the removed region. + * + * @param id the name of the region + * @return a list of removed regions where the first entry is the region specified by {@code id} + */ + @Nullable + public Set removeRegion(String id) { + return removeRegion(id, RemovalStrategy.REMOVE_CHILDREN); + } + + /** + * Remove a region from the index with the given name. + * + * @param id the name of the region + * @param strategy what to do with children + * @return a list of removed regions where the first entry is the region specified by {@code id} + */ + @Nullable + public Set removeRegion(String id, RemovalStrategy strategy) { + return index.remove(id, strategy); + } + + /** + * Query for effective flags and members for the given position. + * + *

{@link QueryOption#COMPUTE_PARENTS} is used.

+ * + * @param position the position + * @return the query object + */ + public ApplicableRegionSet getApplicableRegions(BlockVector3 position) { + return getApplicableRegions(position, QueryOption.COMPUTE_PARENTS); + } + + /** + * Return a region set for the given position. + * + * @param position the position + * @param option the option + * @return a region set + */ + public ApplicableRegionSet getApplicableRegions(BlockVector3 position, QueryOption option) { + checkNotNull(position); + checkNotNull(option); + + Set regions = Sets.newHashSet(); + index.applyContaining(position, option.createIndexConsumer(regions)); + return new RegionResultSet(option.constructResult(regions), index.get("__global__"), true); + } + + /** + * Query for effective flags and members for the area represented + * by the given region. + * + *

{@link QueryOption#COMPUTE_PARENTS} is used.

+ * + * @param region the region + * @return the query object + */ + public ApplicableRegionSet getApplicableRegions(ProtectedRegion region) { + return getApplicableRegions(region, QueryOption.COMPUTE_PARENTS); + } + + /** + * Return a region set for the area represented by the given region. + * + * @param region the region + * @param option the option + * @return a region set + */ + public ApplicableRegionSet getApplicableRegions(ProtectedRegion region, QueryOption option) { + checkNotNull(region); + checkNotNull(option); + + Set regions = Sets.newHashSet(); + index.applyIntersecting(region, option.createIndexConsumer(regions)); + return new RegionResultSet(option.constructResult(regions), index.get("__global__"), true); + } + + /** + * Get a list of region names for regions that contain the given position. + * + * @param position the position + * @return a list of names + */ + public List getApplicableRegionsIDs(BlockVector3 position) { + checkNotNull(position); + + final List names = new ArrayList<>(); + + index.applyContaining(position, region -> names.add(region.getId())); + + return names; + } + + /** + * Return whether there are any regions intersecting the given region that + * are not owned by the given player. + * + * @param region the region + * @param player the player + * @return true if there are such intersecting regions + */ + public boolean overlapsUnownedRegion(ProtectedRegion region, final LocalPlayer player) { + checkNotNull(region); + checkNotNull(player); + + RegionIndex index = this.index; + + final AtomicBoolean overlapsUnowned = new AtomicBoolean(); + + index.applyIntersecting(region, test -> { + if (!test.getOwners().contains(player)) { + overlapsUnowned.set(true); + return false; + } else { + return true; + } + }); + + return overlapsUnowned.get(); + } + + /** + * Get the number of regions. + * + * @return the number of regions + */ + public int size() { + return index.size(); + } + + /** + * Get the number of regions that are owned by the given player. + * + * @param player the player + * @return name number of regions that a player owns + */ + public int getRegionCountOfPlayer(final LocalPlayer player) { + checkNotNull(player); + + final AtomicInteger count = new AtomicInteger(); + + index.apply(test -> { + if (test.getOwners().contains(player)) { + count.incrementAndGet(); + } + return true; + }); + + return count.get(); + } + + /** + * Get an {@link ArrayList} copy of regions in the index with transient regions filtered. + * + * @return a list + */ + private List getFilteredValuesCopy() { + List filteredValues = new ArrayList<>(); + for (ProtectedRegion region : index.values()) { + if (!region.isTransient()) { + filteredValues.add(region); + } + } + return filteredValues; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java new file mode 100644 index 000000000..2665a3624 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/RemovalStrategy.java @@ -0,0 +1,40 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers; + +import com.sk89q.worldguard.protection.managers.index.RegionIndex; + +/** + * Determines the strategy regarding child regions when regions are + * removed from a {@link RegionIndex}. + */ +public enum RemovalStrategy { + + /** + * Unset the parent in children regions. + */ + UNSET_PARENT_IN_CHILDREN, + + /** + * Remove any children under the removed regions. This includes sub-children, etc. + */ + REMOVE_CHILDREN + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java new file mode 100644 index 000000000..c66e9b676 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/AbstractRegionIndex.java @@ -0,0 +1,27 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +/** + * An abstract implementation of a region index to make implementations easier. + */ +abstract class AbstractRegionIndex implements RegionIndex { + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java new file mode 100644 index 000000000..19e45288c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ChunkHashTable.java @@ -0,0 +1,381 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.RegionCollectionConsumer; +import com.sk89q.worldguard.util.collect.LongHashTable; +import com.sk89q.worldguard.util.concurrent.EvenMoreExecutors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Predicate; + +import javax.annotation.Nullable; + +/** + * Maintains a hash table for each chunk containing a list of regions that + * are contained within that chunk, allowing for fast spatial lookup. + */ +public class ChunkHashTable implements ConcurrentRegionIndex { + + private final String name; + private ListeningExecutorService executor = createExecutor(); + private LongHashTable states = new LongHashTable<>(); + private final RegionIndex index; + private final Object lock = new Object(); + @Nullable + private ChunkState lastState; + + /** + * Create a new instance. + * + * @param index the index + * @param name + */ + public ChunkHashTable(RegionIndex index, String name) { + checkNotNull(index); + this.index = index; + this.name = name; + } + + /** + * Create an executor. + * + * @return an executor service + */ + private ListeningExecutorService createExecutor() { + return MoreExecutors.listeningDecorator(EvenMoreExecutors.newBoundedCachedThreadPool(0, 4, Integer.MAX_VALUE, + "WorldGuard Region Chunk Table - " + name)); + } + + /** + * Get a state object at the given position. + * + * @param position the position + * @param create true to create an entry if one does not exist + * @return a chunk state object, or {@code null} (only if {@code create} is false) + */ + @Nullable + private ChunkState get(BlockVector2 position, boolean create) { + ChunkState state; + synchronized (lock) { + state = states.get(position.getBlockX(), position.getBlockZ()); + if (state == null && create) { + state = new ChunkState(position); + states.put(position.getBlockX(), position.getBlockZ(), state); + executor.submit(new EnumerateRegions(position)); + } + } + return state; + } + + /** + * Get a state at the given position or create a new entry if one does + * not exist. + * + * @param position the position + * @return a state + */ + private ChunkState getOrCreate(BlockVector2 position) { + return get(position, true); + } + + /** + * Clear the current hash table and rebuild it in the background. + */ + private void rebuild() { + synchronized (lock) { + ListeningExecutorService previousExecutor = executor; + LongHashTable previousStates = states; + + previousExecutor.shutdownNow(); + states = new LongHashTable<>(); + executor = createExecutor(); + + List positions = new ArrayList<>(); + for (ChunkState state : previousStates.values()) { + BlockVector2 position = state.getPosition(); + positions.add(position); + states.put(position.getBlockX(), position.getBlockZ(), new ChunkState(position)); + } + + if (!positions.isEmpty()) { + executor.submit(new EnumerateRegions(positions)); + } + + lastState = null; + } + } + + /** + * Waits until all currently executing background tasks complete. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return {@code true} if this executor terminated and + * {@code false} if the timeout elapsed before termination + * @throws InterruptedException on interruption + */ + public boolean awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException { + ListeningExecutorService previousExecutor; + synchronized (lock) { + previousExecutor = executor; + executor = createExecutor(); + } + previousExecutor.shutdown(); + return previousExecutor.awaitTermination(timeout, unit); + } + + @Override + public void bias(BlockVector2 chunkPosition) { + checkNotNull(chunkPosition); + getOrCreate(chunkPosition); + } + + @Override + public void biasAll(Collection chunkPositions) { + synchronized (lock) { + for (BlockVector2 position : chunkPositions) { + bias(position); + } + } + } + + @Override + public void forget(BlockVector2 chunkPosition) { + checkNotNull(chunkPosition); + synchronized (lock) { + states.remove(chunkPosition.getBlockX(), chunkPosition.getBlockZ()); + ChunkState state = lastState; + if (state != null && state.getPosition().getBlockX() == chunkPosition.getBlockX() && state.getPosition().getBlockZ() == chunkPosition.getBlockZ()) { + lastState = null; + } + } + } + + @Override + public void forgetAll() { + synchronized (lock) { + executor.shutdownNow(); + states = new LongHashTable<>(); + executor = createExecutor(); + lastState = null; + } + } + + @Override + public void add(ProtectedRegion region) { + index.add(region); + rebuild(); + } + + @Override + public void addAll(Collection regions) { + index.addAll(regions); + rebuild(); + } + + @Override + public Set remove(String id, RemovalStrategy strategy) { + Set removed = index.remove(id, strategy); + rebuild(); + return removed; + } + + @Override + public boolean contains(String id) { + return index.contains(id); + } + + @Nullable + @Override + public ProtectedRegion get(String id) { + return index.get(id); + } + + @Override + public void apply(Predicate consumer) { + index.apply(consumer); + } + + @Override + public void applyContaining(BlockVector3 position, Predicate consumer) { + checkNotNull(position); + checkNotNull(consumer); + + ChunkState state = lastState; + int chunkX = position.getBlockX() >> 4; + int chunkZ = position.getBlockZ() >> 4; + + if (state == null || state.getPosition().getBlockX() != chunkX || state.getPosition().getBlockZ() != chunkZ) { + state = get(BlockVector2.at(chunkX, chunkZ), false); + } + + if (state != null && state.isLoaded()) { + for (ProtectedRegion region : state.getRegions()) { + if (region.contains(position)) { + consumer.test(region); + } + } + } else { + index.applyContaining(position, consumer); + } + } + + @Override + public void applyIntersecting(ProtectedRegion region, Predicate consumer) { + index.applyIntersecting(region, consumer); + } + + @Override + public int size() { + return index.size(); + } + + @Override + public RegionDifference getAndClearDifference() { + return index.getAndClearDifference(); + } + + @Override + public void setDirty(RegionDifference difference) { + index.setDirty(difference); + } + + @Override + public Collection values() { + return index.values(); + } + + @Override + public boolean isDirty() { + return index.isDirty(); + } + + @Override + public void setDirty(boolean dirty) { + index.setDirty(dirty); + } + + /** + * A task to enumerate the regions for a list of provided chunks. + */ + private class EnumerateRegions implements Runnable { + private final List positions; + + private EnumerateRegions(BlockVector2 position) { + this(Arrays.asList(checkNotNull(position))); + } + + private EnumerateRegions(List positions) { + checkNotNull(positions); + checkArgument(!positions.isEmpty(), "List of positions can't be empty"); + this.positions = positions; + } + + @Override + public void run() { + for (BlockVector2 position : positions) { + ChunkState state = get(position, false); + + if (state != null) { + List regions = new ArrayList<>(); + ProtectedRegion chunkRegion = new ProtectedCuboidRegion( + "_", + position.multiply(16).toBlockVector3(Integer.MIN_VALUE), + position.add(1, 1).multiply(16).toBlockVector3(Integer.MAX_VALUE)); + index.applyIntersecting(chunkRegion, new RegionCollectionConsumer(regions, false)); + Collections.sort(regions); + + state.setRegions(Collections.unmodifiableList(regions)); + + if (Thread.currentThread().isInterrupted()) { + return; + } + } + } + } + } + + /** + * Stores a cache of region data for a chunk. + */ + private class ChunkState { + private final BlockVector2 position; + private boolean loaded = false; + private List regions = Collections.emptyList(); + + private ChunkState(BlockVector2 position) { + this.position = position; + } + + public BlockVector2 getPosition() { + return position; + } + + public List getRegions() { + return regions; + } + + public void setRegions(List regions) { + this.regions = regions; + this.loaded = true; + } + + public boolean isLoaded() { + return loaded; + } + } + + /** + * A factory for instances of {@code ChunkHashCache}. + */ + public static class Factory implements Function { + private final Function supplier; + + public Factory(Function supplier) { + checkNotNull(supplier); + this.supplier = supplier; + } + + @Override + public ChunkHashTable apply(String name) { + return new ChunkHashTable(supplier.apply(name), name); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java new file mode 100644 index 000000000..0f8ff47a1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/ConcurrentRegionIndex.java @@ -0,0 +1,32 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import java.util.concurrent.ConcurrentMap; + +/** + * An implementation of a region index that supports concurrent access. + * + *

The mechanics of concurrent access should be similar to that of + * {@link ConcurrentMap}. Spatial queries can lag behind changes on the data + * for brief periods of time.

+ */ +public interface ConcurrentRegionIndex extends RegionIndex { +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java new file mode 100644 index 000000000..c9a496585 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/HashMapIndex.java @@ -0,0 +1,314 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldguard.util.Normal.normalize; + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.logging.Level; + +/** + * An index that stores regions in a hash map, which allows for fast lookup + * by ID but O(n) performance for spatial queries. + * + *

This implementation supports concurrency to the extent that + * a {@link ConcurrentMap} does.

+ */ +public class HashMapIndex extends AbstractRegionIndex implements ConcurrentRegionIndex { + + private final ConcurrentMap regions = new ConcurrentHashMap<>(); + private Set removed = new HashSet<>(); + private final Object lock = new Object(); + + /** + * Called to rebuild the index after changes. + */ + protected void rebuildIndex() { + // Can be implemented by subclasses + } + + /** + * Perform the add operation. + * + * @param region the region + */ + private void performAdd(ProtectedRegion region) { + checkNotNull(region); + + region.setDirty(true); + + synchronized (lock) { + String normalId = normalize(region.getId()); + + ProtectedRegion existing = regions.get(normalId); + + if (existing != null) { + removeAndReplaceParents(existing.getId(), RemovalStrategy.UNSET_PARENT_IN_CHILDREN, region, false); + + // Casing / form of ID has not changed + if (existing.getId().equals(region.getId())) { + removed.remove(existing); + } + } + + regions.put(normalId, region); + + removed.remove(region); + + ProtectedRegion parent = region.getParent(); + if (parent != null) { + performAdd(parent); + } + } + } + + @Override + public void addAll(Collection regions) { + checkNotNull(regions); + + synchronized (lock) { + for (ProtectedRegion region : regions) { + performAdd(region); + } + + rebuildIndex(); + } + } + + @Override + public void bias(BlockVector2 chunkPosition) { + // Nothing to do + } + + @Override + public void biasAll(Collection chunkPositions) { + // Nothing to do + } + + @Override + public void forget(BlockVector2 chunkPosition) { + // Nothing to do + } + + @Override + public void forgetAll() { + // Nothing to do + } + + @Override + public void add(ProtectedRegion region) { + synchronized (lock) { + performAdd(region); + + rebuildIndex(); + } + } + + @Override + public Set remove(String id, RemovalStrategy strategy) { + return removeAndReplaceParents(id, strategy, null, true); + } + + private Set removeAndReplaceParents(String id, RemovalStrategy strategy, @Nullable ProtectedRegion replacement, boolean rebuildIndex) { + checkNotNull(id); + checkNotNull(strategy); + + Set removedSet = new HashSet<>(); + + synchronized (lock) { + ProtectedRegion removed = regions.remove(normalize(id)); + + if (removed != null) { + removedSet.add(removed); + + while (true) { + int lastSize = removedSet.size(); + Iterator it = regions.values().iterator(); + + // Handle children + while (it.hasNext()) { + ProtectedRegion current = it.next(); + ProtectedRegion parent = current.getParent(); + + if (parent != null && removedSet.contains(parent)) { + switch (strategy) { + case REMOVE_CHILDREN: + removedSet.add(current); + it.remove(); + break; + case UNSET_PARENT_IN_CHILDREN: + try { + current.setParent(replacement); + } catch (CircularInheritanceException e) { + WorldGuard.logger.log(Level.WARNING, "Failed to replace parent '" + parent.getId() + "' of child '" + current.getId() + "' with replacement '" + replacement.getId() + "'", e); + current.clearParent(); + } + } + } + } + if (strategy == RemovalStrategy.UNSET_PARENT_IN_CHILDREN + || removedSet.size() == lastSize) { + break; + } + } + } + + this.removed.addAll(removedSet); + + if (rebuildIndex) { + rebuildIndex(); + } + } + + return removedSet; + } + + @Override + public boolean contains(String id) { + return regions.containsKey(normalize(id)); + } + + @Nullable + @Override + public ProtectedRegion get(String id) { + return regions.get(normalize(id)); + } + + @Override + public void apply(Predicate consumer) { + for (ProtectedRegion region : regions.values()) { + if (!consumer.test(region)) { + break; + } + } + } + + @Override + public void applyContaining(final BlockVector3 position, final Predicate consumer) { + apply(region -> !region.contains(position) || consumer.test(region)); + } + + @Override + public void applyIntersecting(ProtectedRegion region, Predicate consumer) { + for (ProtectedRegion found : region.getIntersectingRegions(regions.values())) { + if (!consumer.test(found)) { + break; + } + } + } + + @Override + public int size() { + return regions.size(); + } + + @Override + public RegionDifference getAndClearDifference() { + synchronized (lock) { + Set changed = new HashSet<>(); + Set removed = this.removed; + + for (ProtectedRegion region : regions.values()) { + if (region.isDirty()) { + changed.add(region); + region.setDirty(false); + } + } + + this.removed = new HashSet<>(); + + return new RegionDifference(changed, removed); + } + } + + @Override + public void setDirty(RegionDifference difference) { + synchronized (lock) { + for (ProtectedRegion changed : difference.getChanged()) { + changed.setDirty(true); + } + removed.addAll(difference.getRemoved()); + } + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(regions.values()); + } + + @Override + public boolean isDirty() { + synchronized (lock) { + if (!removed.isEmpty()) { + return true; + } + + for (ProtectedRegion region : regions.values()) { + if (region.isDirty()) { + return true; + } + } + } + + return false; + } + + @Override + public void setDirty(boolean dirty) { + synchronized (lock) { + if (!dirty) { + removed.clear(); + } + + for (ProtectedRegion region : regions.values()) { + region.setDirty(dirty); + } + } + } + + /** + * A factory for new instances using this index. + */ + public static final class Factory implements Function { + @Override + public HashMapIndex apply(String name) { + return new HashMapIndex(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java new file mode 100644 index 000000000..0a588b7b5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/PriorityRTreeIndex.java @@ -0,0 +1,113 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegionMBRConverter; +import org.khelekore.prtree.MBR; +import org.khelekore.prtree.MBRConverter; +import org.khelekore.prtree.PRTree; +import org.khelekore.prtree.SimpleMBR; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * An implementation of an index that uses {@link HashMapIndex} for queries + * by region name and a priority R-tree for spatial queries. + * + *

At the moment, the R-tree is only utilized for the + * {@link #applyContaining(BlockVector3, Predicate)} method, and the underlying + * hash map-based index is used for the other spatial queries. In addition, + * every modification to the index requires the entire R-tree to be rebuilt, + * although this operation is reasonably quick.

+ * + *

This implementation is as thread-safe as the underlying + * {@link HashMapIndex}, although spatial queries may lag behind changes + * for very brief periods of time as the tree is rebuilt.

+ */ +public class PriorityRTreeIndex extends HashMapIndex { + + private static final int BRANCH_FACTOR = 30; + private static final MBRConverter CONVERTER = new ProtectedRegionMBRConverter(); + + private PRTree tree; + + public PriorityRTreeIndex() { + tree = new PRTree<>(CONVERTER, BRANCH_FACTOR); + tree.load(Collections.emptyList()); + } + + @Override + protected void rebuildIndex() { + PRTree newTree = new PRTree<>(CONVERTER, BRANCH_FACTOR); + newTree.load(values()); + this.tree = newTree; + } + + @Override + public void applyContaining(BlockVector3 position, Predicate consumer) { + Set seen = new HashSet<>(); + MBR pointMBR = new SimpleMBR(position.getX(), position.getX(), position.getY(), position.getY(), position.getZ(), position.getZ()); + + for (ProtectedRegion region : tree.find(pointMBR)) { + if (region.contains(position) && !seen.contains(region)) { + seen.add(region); + if (!consumer.test(region)) { + break; + } + } + } + } + + @Override + public void applyIntersecting(ProtectedRegion region, Predicate consumer) { + BlockVector3 min = region.getMinimumPoint().floor(); + BlockVector3 max = region.getMaximumPoint().ceil(); + + Set candidates = new HashSet<>(); + MBR pointMBR = new SimpleMBR(min.getX(), max.getX(), min.getY(), max.getY(), min.getZ(), max.getZ()); + + for (ProtectedRegion found : tree.find(pointMBR)) { + candidates.add(found); + } + + for (ProtectedRegion found : region.getIntersectingRegions(candidates)) { + if (!consumer.test(found)) { + break; + } + } + } + + /** + * A factory for new instances using this index. + */ + public static final class Factory implements Function { + @Override + public PriorityRTreeIndex apply(String name) { + return new PriorityRTreeIndex(); + } + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java new file mode 100644 index 000000000..1300e0dc1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/index/RegionIndex.java @@ -0,0 +1,178 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.index; + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.ChangeTracked; +import com.sk89q.worldguard.util.Normal; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Predicate; + +import javax.annotation.Nullable; + +/** + * An index of regions to allow for fast lookups of regions by their ID and + * through spatial queries. + * + *

Indexes may be thread-unsafe.

+ */ +public interface RegionIndex extends ChangeTracked { + + /** + * Bias the given chunk for faster lookups (put it in a hash table, etc.). + * + *

Implementations may choose to do nothing.

+ * + * @param chunkPosition the chunk position + */ + void bias(BlockVector2 chunkPosition); + + /** + * Bias the given chunk for faster lookups (put it in a hash table, etc.). + * + *

Implementations may choose to do nothing.

+ * + * @param chunkPosition the chunk position + */ + void biasAll(Collection chunkPosition); + + /** + * No longer bias the given chunk for faster lookup. + * + * @param chunkPosition the chunk position + */ + void forget(BlockVector2 chunkPosition); + + /** + * Clearly all extra cache data created by any calls to + * {@link #bias(BlockVector2)}. + */ + void forgetAll(); + + /** + * Add a region to this index, replacing any existing one with the same + * name (equality determined using {@link Normal}). + * + *

The parents of the region will also be added to the index.

+ * + * @param region the region + */ + void add(ProtectedRegion region); + + /** + * Add a list of regions to this index, replacing any existing one + * with the same name (equality determined using {@link Normal}). + * + *

The parents of the region will also be added to the index.

+ * + * @param regions a collections of regions + */ + void addAll(Collection regions); + + /** + * Remove a region from the index with the given name. + * + * @param id the name of the region + * @param strategy what to do with children + * @return a list of removed regions where the first entry is the region specified by {@code id} + */ + Set remove(String id, RemovalStrategy strategy); + + /** + * Test whether the index contains a region named by the given name + * (equality determined using {@link Normal}). + * + * @param id the name of the region + * @return true if the index contains the region + */ + boolean contains(String id); + + /** + * Get the region named by the given name (equality determined using + * {@link Normal}). + * + * @param id the name of the region + * @return a region or {@code null} + */ + @Nullable + ProtectedRegion get(String id); + + /** + * Apply the given predicate to all the regions in the index + * until there are no more regions or the predicate returns false. + * + * @param consumer a predicate that returns true to continue iterating + */ + void apply(Predicate consumer); + + /** + * Apply the given predicate to all regions that contain the given + * position until there are no more regions or the predicate returns false. + * + * @param position the position + * @param consumer a predicate that returns true to continue iterating + */ + void applyContaining(BlockVector3 position, Predicate consumer); + + /** + * Apply the given predicate to all regions that intersect the given + * region until there are no more regions or the predicate returns false. + * + * @param region the intersecting region + * @param consumer a predicate that returns true to continue iterating + */ + void applyIntersecting(ProtectedRegion region, Predicate consumer); + + /** + * Return the number of regions in the index. + * + * @return the number of regions + */ + int size(); + + /** + * Get the list of changed or removed regions since last call and + * clear those lists. + * + * @return the difference + */ + RegionDifference getAndClearDifference(); + + /** + * Set the index to be dirty using the given difference. + * + * @param difference the difference + */ + void setDirty(RegionDifference difference); + + /** + * Get an unmodifiable collection of regions stored in this index. + * + * @return a collection of regions + */ + Collection values(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java new file mode 100644 index 000000000..22a62c411 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/AbstractMigration.java @@ -0,0 +1,82 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An abstract implementation of a migrator that gets all the worlds in + * a driver and calls a override-able {@code migrate()} method for + * each store. + */ +abstract class AbstractMigration implements Migration { + + private static final Logger log = Logger.getLogger(AbstractMigration.class.getCanonicalName()); + private final RegionDriver driver; + + /** + * Create a new instance. + * + * @param driver the storage driver + */ + public AbstractMigration(RegionDriver driver) { + checkNotNull(driver); + + this.driver = driver; + } + + @Override + public final void migrate() throws MigrationException { + try { + for (RegionDatabase store : driver.getAll()) { + try { + migrate(store); + } catch (MigrationException e) { + log.log(Level.WARNING, "Migration of one world (" + store.getName() + ") failed with an error", e); + } + } + + postMigration(); + } catch (StorageException e) { + throw new MigrationException("Migration failed because the process of getting a list of all the worlds to migrate failed", e); + } + } + + /** + * Called for all the worlds in the driver. + * + * @param store the region store + * @throws MigrationException on migration error + */ + protected abstract void migrate(RegionDatabase store)throws MigrationException; + + /** + * Called after migration has successfully completed. + */ + protected abstract void postMigration(); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/DriverMigration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/DriverMigration.java new file mode 100644 index 000000000..c4754aa9f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/DriverMigration.java @@ -0,0 +1,89 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +import com.google.common.base.Preconditions; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Set; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Handles migration from one region store driver to another. + */ +public class DriverMigration extends AbstractMigration { + + private static final Logger log = Logger.getLogger(DriverMigration.class.getCanonicalName()); + private final RegionDriver target; + private final FlagRegistry flagRegistry; + + /** + * Create a new instance. + * + * @param driver the source storage driver + * @param target the target storage driver + * @param flagRegistry the flag registry + */ + public DriverMigration(RegionDriver driver, RegionDriver target, FlagRegistry flagRegistry) { + super(driver); + checkNotNull(target); + Preconditions.checkNotNull(flagRegistry, "flagRegistry"); + this.target = target; + this.flagRegistry = flagRegistry; + } + + @Override + protected void migrate(RegionDatabase store) throws MigrationException { + Set regions; + + log.info("Loading the regions for '" + store.getName() + "' with the old driver..."); + + try { + regions = store.loadAll(flagRegistry); + } catch (StorageException e) { + throw new MigrationException("Failed to load region data for the world '" + store.getName() + "'", e); + } + + write(store.getName(), regions); + } + + private void write(String name, Set regions) throws MigrationException { + log.info("Saving the data for '" + name + "' with the new driver..."); + + RegionDatabase store = target.get(name); + + try { + store.saveAll(regions); + } catch (StorageException e) { + throw new MigrationException("Failed to save region data for '" + store.getName() + "' to the new driver", e); + } + } + + @Override + protected void postMigration() { + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/Migration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/Migration.java new file mode 100644 index 000000000..637ffe19f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/Migration.java @@ -0,0 +1,34 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +/** + * An object that migrates region data. + */ +public interface Migration { + + /** + * Execute the migration. + * + * @throws MigrationException thrown if the migration fails + */ + void migrate() throws MigrationException; + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/MigrationException.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/MigrationException.java new file mode 100644 index 000000000..c045b003c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/MigrationException.java @@ -0,0 +1,43 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +/** + * Thrown when a migration fails. + */ +public class MigrationException extends Exception { + + public MigrationException() { + super(); + } + + public MigrationException(String message) { + super(message); + } + + public MigrationException(String message, Throwable cause) { + super(message, cause); + } + + public MigrationException(Throwable cause) { + super(cause); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/UUIDMigration.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/UUIDMigration.java new file mode 100644 index 000000000..36e91621b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/migration/UUIDMigration.java @@ -0,0 +1,246 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.migration; + +import com.google.common.base.Predicate; +import com.sk89q.worldguard.util.profile.Profile; +import com.sk89q.worldguard.util.profile.resolver.ProfileService; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.domains.PlayerDomain; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Migrates names to UUIDs for all the worlds in a region store. + */ +public class UUIDMigration extends AbstractMigration { + + private static final Logger log = Logger.getLogger(UUIDMigration.class.getCanonicalName()); + private static final int LOG_DELAY = 5000; + + private final Timer timer = new Timer("WorldGuard UUID Migration"); + private final ProfileService profileService; + private final FlagRegistry flagRegistry; + private final ConcurrentMap resolvedNames = new ConcurrentHashMap<>(); + private final Set unresolvedNames = new HashSet<>(); + private boolean keepUnresolvedNames = true; + + /** + * Create a new instance. + * + * @param driver the storage driver + * @param profileService the profile service + * @param flagRegistry the flag registry + */ + public UUIDMigration(RegionDriver driver, ProfileService profileService, FlagRegistry flagRegistry) { + super(driver); + checkNotNull(profileService); + checkNotNull(flagRegistry, "flagRegistry"); + this.profileService = profileService; + this.flagRegistry = flagRegistry; + } + + @Override + protected void migrate(RegionDatabase store) throws MigrationException { + log.log(Level.INFO, "Migrating regions in '" + store.getName() + "' to convert names -> UUIDs..."); + + Set regions; + + try { + regions = store.loadAll(flagRegistry); + } catch (StorageException e) { + throw new MigrationException("Failed to load region data for the world '" + store.getName() + "'", e); + } + + migrate(regions); + + try { + store.saveAll(regions); + } catch (StorageException e) { + throw new MigrationException("Failed to save region data after migration of the world '" + store.getName() + "'", e); + } + } + + private boolean migrate(Collection regions) throws MigrationException { + // Name scan pass + Set names = getNames(regions); + + if (!names.isEmpty()) { + // This task logs the progress of conversion (% converted...) + // periodically + TimerTask task = new ResolvedNamesTimerTask(); + + try { + timer.schedule(task, LOG_DELAY, LOG_DELAY); + + log.log(Level.INFO, "Resolving " + names.size() + " name(s) into UUIDs... this may take a while."); + + // Don't lookup names that we already looked up for previous + // worlds -- note: all names are lowercase in these collections + Set lookupNames = new HashSet<>(names); + lookupNames.removeAll(resolvedNames.keySet()); + + // Ask Mojang for names + profileService.findAllByName(lookupNames, new Predicate() { + @Override + public boolean apply(Profile profile) { + resolvedNames.put(profile.getName().toLowerCase(), profile.getUniqueId()); + return true; + } + }); + } catch (IOException e) { + throw new MigrationException("The name -> UUID service failed", e); + } catch (InterruptedException e) { + throw new MigrationException("The migration was interrupted"); + } finally { + // Stop showing the % converted messages + task.cancel(); + } + + // Name -> UUID in all regions + log.log(Level.INFO, "UUIDs resolved... now migrating all regions to UUIDs where possible..."); + convert(regions); + + return true; + } else { + return false; + } + } + + @Override + protected void postMigration() { + if (!unresolvedNames.isEmpty()) { + if (keepUnresolvedNames) { + log.log(Level.WARNING, + "Some member and owner names do not seem to exist or own Minecraft so they " + + "could not be converted into UUIDs. They have been left as names, but the conversion can " + + "be re-run with 'keep-names-that-lack-uuids' set to false in the configuration in " + + "order to remove these names. Leaving the names means that someone can register with one of " + + "these names in the future and become that player."); + } else { + log.log(Level.WARNING, + "Some member and owner names do not seem to exist or own Minecraft so they " + + "could not be converted into UUIDs. These names have been removed."); + } + } + } + + /** + * Grab all the player names from all the regions in the given collection. + * + * @param regions a collection of regions + * @return a set of names + */ + private static Set getNames(Collection regions) { + Set names = new HashSet<>(); + for (ProtectedRegion region : regions) { + // Names are already lower case + names.addAll(region.getOwners().getPlayers()); + names.addAll(region.getMembers().getPlayers()); + } + return names; + } + + /** + * Convert all the names to UUIDs. + * + * @param regions a collection of regions + */ + private void convert(Collection regions) { + for (ProtectedRegion region : regions) { + convert(region.getOwners()); + convert(region.getMembers()); + } + } + + /** + * Convert all the names to UUIDs. + * + * @param domain the domain + */ + private void convert(DefaultDomain domain) { + PlayerDomain playerDomain = new PlayerDomain(); + for (UUID uuid : domain.getUniqueIds()) { + playerDomain.addPlayer(uuid); + } + + for (String name : domain.getPlayers()) { + UUID uuid = resolvedNames.get(name.toLowerCase()); + if (uuid != null) { + playerDomain.addPlayer(uuid); + } else { + if (keepUnresolvedNames) { + playerDomain.addPlayer(name); + } + unresolvedNames.add(name); + } + } + + domain.setPlayerDomain(playerDomain); + } + + /** + * Get whether names that have no UUID equivalent (i.e. name that is not + * owned) should be kept as names and not removed. + * + * @return true to keep names + */ + public boolean getKeepUnresolvedNames() { + return keepUnresolvedNames; + } + + /** + * Set whether names that have no UUID equivalent (i.e. name that is not + * owned) should be kept as names and not removed. + * + * @param keepUnresolvedNames true to keep names + */ + public void setKeepUnresolvedNames(boolean keepUnresolvedNames) { + this.keepUnresolvedNames = keepUnresolvedNames; + } + + /** + * A task to periodically say how many names have been resolved. + */ + private class ResolvedNamesTimerTask extends TimerTask { + @Override + public void run() { + log.info("UUIDs have been found for " + resolvedNames.size() + " name(s)..."); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java new file mode 100644 index 000000000..b6d109397 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DifferenceSaveException.java @@ -0,0 +1,41 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +/** + * Thrown when a partial save is not supported. + */ +public class DifferenceSaveException extends StorageException { + + public DifferenceSaveException() { + } + + public DifferenceSaveException(String message) { + super(message); + } + + public DifferenceSaveException(String message, Throwable cause) { + super(message, cause); + } + + public DifferenceSaveException(Throwable cause) { + super(cause); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DriverType.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DriverType.java new file mode 100644 index 000000000..0e0ea6389 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/DriverType.java @@ -0,0 +1,30 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +/** + * An enumeration of supported drivers. + */ +public enum DriverType { + + YAML, + MYSQL + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionDatabase.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionDatabase.java new file mode 100644 index 000000000..8426f8095 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/MemoryRegionDatabase.java @@ -0,0 +1,60 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A region database that saves the memory to an in-memory {@link HashSet}. + * + *

This implementation is thread-safe. Difference saves + * are not supported.

+ */ +public class MemoryRegionDatabase implements RegionDatabase { + + private Set regions = Collections.emptySet(); + + @Override + public String getName() { + return "MEMORY"; + } + + @Override + public Set loadAll(FlagRegistry flagRegistry) { + return regions; + } + + @Override + public void saveAll(Set regions) { + this.regions = Collections.unmodifiableSet(new HashSet<>(regions)); + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException { + throw new DifferenceSaveException(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabase.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabase.java new file mode 100644 index 000000000..e847b5c64 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabase.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Set; + +/** + * A region database stores a set of regions for a single world. + * + *

If there are multiple worlds, then there should be one region database + * per world. To manage multiple region databases, consider using an + * implementation of a {@link RegionDriver}.

+ * + * @see RegionDriver + */ +public interface RegionDatabase { + + /** + * Get a displayable name for this store. + */ + String getName(); + + /** + * Load all regions from storage and place them into the passed map. + * + *

The map will only be modified from one thread. The keys of + * each map entry will be in respect to the ID of the region but + * transformed to be lowercase. Until this method returns, the map may not + * be modified by any other thread simultaneously. If an exception is + * thrown, then the state in which the map is left is undefined.

+ * + *

The provided map should have reasonably efficient + * {@code get()} and {@code put()} calls in order to maximize performance. + *

+ * + * @param flags a flag registry + * @return a set of loaded regions + * @throws StorageException thrown on read error + */ + Set loadAll(FlagRegistry flags) throws StorageException; + + /** + * Replace all the data in the store with the given collection of regions. + * + * @param regions a set of regions + * @throws StorageException thrown on write error + */ + void saveAll(Set regions) throws StorageException; + + /** + * Perform a partial save that only commits changes, rather than the + * entire region index. + * + * @param difference the difference + * @throws DifferenceSaveException thrown if partial saves are not supported + * @throws StorageException thrown on write error + */ + void saveChanges(RegionDifference difference) throws DifferenceSaveException, StorageException; + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabaseUtils.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabaseUtils.java new file mode 100644 index 000000000..dc5e5e216 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDatabaseUtils.java @@ -0,0 +1,69 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; + +import java.util.Map; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * This class provides utility methods that may be helpful in the + * implementation of region databases. + * + * @see RegionDatabase + */ +public final class RegionDatabaseUtils { + + private static final Logger log = Logger.getLogger(RegionDatabaseUtils.class.getCanonicalName()); + + private RegionDatabaseUtils() { + } + + /** + * Re-link parent regions on each provided region using the two + * provided maps. + * + * @param regions the map of regions from which parent regions are found + * @param parentSets a mapping of region to parent name + */ + public static void relinkParents(Map regions, Map parentSets) { + checkNotNull(regions); + checkNotNull(parentSets); + + for (Map.Entry entry : parentSets.entrySet()) { + ProtectedRegion target = entry.getKey(); + ProtectedRegion parent = regions.get(entry.getValue()); + if (parent != null) { + try { + target.setParent(parent); + } catch (CircularInheritanceException e) { + log.warning("Circular inheritance detected! Can't set the parent of '" + target + "' to parent '" + parent.getId() + "'"); + } + } else { + log.warning("Unknown region parent: " + entry.getValue()); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDriver.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDriver.java new file mode 100644 index 000000000..b2dd7d39c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/RegionDriver.java @@ -0,0 +1,61 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +import java.util.List; + +/** + * A driver manages {@link RegionDatabase}s for several worlds. An instance + * can return instances of a database for any given world. + * + * @see RegionDatabase + */ +public interface RegionDriver { + + /** + * Get a region database for a world. + * + *

The given name should be a unique name for the world. Due to + * legacy reasons, there are no stipulations on the case sensitivity + * of the name. Historically, however, if the driver is a file-based + * driver, case-sensitivity will vary on whether the underlying + * filesystem is case-sensitive.

+ * + *

This method should return quickly.

+ * + * @param name the name of the world, which may be case sensitive + * @return the world + */ + RegionDatabase get(String name); + + /** + * Fetch all the region databases that have been stored using this driver. + * Essentially, return a region database for all worlds that have had + * regions saved for it in the past. + * + *

As this may require a query to be performed, this method may block + * for a prolonged period of time.

+ * + * @return a list of databases + * @throws StorageException thrown if the fetch operation fails + */ + List getAll() throws StorageException; + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/StorageException.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/StorageException.java new file mode 100644 index 000000000..ed5aa5922 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/StorageException.java @@ -0,0 +1,42 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage; + +/** + * Exceptions related to region stores inherit from this exception. + */ +public class StorageException extends Exception { + + public StorageException() { + } + + public StorageException(String message) { + super(message); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } + + public StorageException(Throwable cause) { + super(cause); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DirectoryYamlDriver.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DirectoryYamlDriver.java new file mode 100644 index 000000000..bb6e00a5c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/DirectoryYamlDriver.java @@ -0,0 +1,98 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores region data in a {root_dir}/{id}/{filename} pattern on disk + * using {@link YamlRegionFile}. + */ +public class DirectoryYamlDriver implements RegionDriver { + + private final File rootDir; + private final String filename; + + /** + * Create a new instance. + * + * @param rootDir the directory where the world folders reside + * @param filename the filename (i.e. "regions.yml") + */ + public DirectoryYamlDriver(File rootDir, String filename) { + checkNotNull(rootDir); + checkNotNull(filename); + this.rootDir = rootDir; + this.filename = filename; + } + + /** + * Get the path for the given ID. + * + * @param id the ID + * @return the file path + */ + private File getPath(String id) { + checkNotNull(id); + + File f = new File(rootDir, id + File.separator + filename); + try { + f.getCanonicalPath(); + return f; + } catch (IOException e) { + throw new IllegalArgumentException("Invalid file path for the world's regions file"); + } + } + + @Override + public RegionDatabase get(String id) { + checkNotNull(id); + + File file = getPath(id); + + return new YamlRegionFile(id, file); + } + + @Override + public List getAll() throws StorageException { + List stores = new ArrayList<>(); + + File files[] = rootDir.listFiles(); + if (files != null) { + for (File dir : files) { + if (dir.isDirectory() && new File(dir, "regions.yml").isFile()) { + stores.add(new YamlRegionFile(dir.getName(), getPath(dir.getName()))); + } + } + } + + return stores; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java new file mode 100644 index 000000000..9aebc5ac7 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/file/YamlRegionFile.java @@ -0,0 +1,349 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.file; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.util.yaml.YAMLFormat; +import com.sk89q.util.yaml.YAMLNode; +import com.sk89q.util.yaml.YAMLProcessor; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.FlagUtil; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabaseUtils; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.parser.ParserException; +import org.yaml.snakeyaml.representer.Representer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A store that persists regions in a YAML-encoded file. + */ +public class YamlRegionFile implements RegionDatabase { + + private static final Logger log = Logger.getLogger(YamlRegionFile.class.getCanonicalName()); + private static final Yaml ERROR_DUMP_YAML; + + private static final String FILE_HEADER = "#\r\n" + + "# WorldGuard regions file\r\n" + + "#\r\n" + + "# WARNING: THIS FILE IS AUTOMATICALLY GENERATED. If you modify this file by\r\n" + + "# hand, be aware that A SINGLE MISTYPED CHARACTER CAN CORRUPT THE FILE. If\r\n" + + "# WorldGuard is unable to parse the file, your regions will FAIL TO LOAD and\r\n" + + "# the contents of this file will reset. Please use a YAML validator such as\r\n" + + "# http://yaml-online-parser.appspot.com (for smaller files).\r\n" + + "#\r\n" + + "# REMEMBER TO KEEP PERIODICAL BACKUPS.\r\n" + + "#"; + + private final String name; + private final File file; + + static { + DumperOptions options = new DumperOptions(); + options.setIndent(4); + options.setDefaultFlowStyle(FlowStyle.AUTO); + + ERROR_DUMP_YAML = new Yaml(new SafeConstructor(), new Representer(), options); + } + + /** + * Create a new instance. + * + * @param name the name of this store + * @param file the file + */ + public YamlRegionFile(String name, File file) { + checkNotNull(name, "name"); + checkNotNull(file, "file"); + this.name = name; + this.file = file; + } + + @Override + public String getName() { + return name; + } + + @Override + public Set loadAll(FlagRegistry flagRegistry) throws StorageException { + Map loaded = new HashMap<>(); + + YAMLProcessor config = createYamlProcessor(file); + try { + config.load(); + } catch (FileNotFoundException e) { + return new HashSet<>(loaded.values()); + } catch (IOException | ParserException e) { + throw new StorageException("Failed to load region data from '" + file + "'", e); + } + + Map regionData = config.getNodes("regions"); + + if (regionData == null) { + return Collections.emptySet(); // No regions are even configured + } + + Map parentSets = new LinkedHashMap<>(); + + for (Map.Entry entry : regionData.entrySet()) { + String id = entry.getKey(); + YAMLNode node = entry.getValue(); + + String type = node.getString("type"); + ProtectedRegion region; + + try { + if (type == null) { + log.warning("Undefined region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } else if (type.equals("cuboid")) { + Vector3 pt1 = checkNotNull(node.getVector("min")); + Vector3 pt2 = checkNotNull(node.getVector("max")); + BlockVector3 min = pt1.getMinimum(pt2).toBlockPoint(); + BlockVector3 max = pt1.getMaximum(pt2).toBlockPoint(); + region = new ProtectedCuboidRegion(id, min, max); + } else if (type.equals("poly2d")) { + Integer minY = checkNotNull(node.getInt("min-y")); + Integer maxY = checkNotNull(node.getInt("max-y")); + List points = node.getBlockVector2List("points", null); + region = new ProtectedPolygonalRegion(id, points, minY, maxY); + } else if (type.equals("global")) { + region = new GlobalProtectedRegion(id); + } else { + log.warning("Unknown region type for region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + "\n"); + continue; + } + + Integer priority = checkNotNull(node.getInt("priority")); + region.setPriority(priority); + setFlags(flagRegistry, region, node.getNode("flags")); + region.setOwners(parseDomain(node.getNode("owners"))); + region.setMembers(parseDomain(node.getNode("members"))); + + loaded.put(id, region); + + String parentId = node.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } catch (NullPointerException e) { + log.log(Level.WARNING, + "Unexpected NullPointerException encountered during parsing for the region '" + id + "'!\n" + + "Here is what the region data looks like:\n\n" + toYamlOutput(entry.getValue().getMap()) + + "\n\nNote: This region will disappear as a result!", e); + } + } + + // Relink parents + RegionDatabaseUtils.relinkParents(loaded, parentSets); + + return new HashSet<>(loaded.values()); + } + + @Override + public void saveAll(Set regions) throws StorageException { + checkNotNull(regions); + + File tempFile = new File(file.getParentFile(), file.getName() + ".tmp"); + YAMLProcessor config = createYamlProcessor(tempFile); + + config.clear(); + + YAMLNode regionsNode = config.addNode("regions"); + Map map = regionsNode.getMap(); + + for (ProtectedRegion region : regions) { + Map nodeMap = new HashMap<>(); + map.put(region.getId(), nodeMap); + YAMLNode node = new YAMLNode(nodeMap, false); + + if (region instanceof ProtectedCuboidRegion) { + ProtectedCuboidRegion cuboid = (ProtectedCuboidRegion) region; + node.setProperty("type", "cuboid"); + node.setProperty("min", cuboid.getMinimumPoint()); + node.setProperty("max", cuboid.getMaximumPoint()); + } else if (region instanceof ProtectedPolygonalRegion) { + ProtectedPolygonalRegion poly = (ProtectedPolygonalRegion) region; + node.setProperty("type", "poly2d"); + node.setProperty("min-y", poly.getMinimumPoint().getBlockY()); + node.setProperty("max-y", poly.getMaximumPoint().getBlockY()); + + List> points = new ArrayList<>(); + for (BlockVector2 point : poly.getPoints()) { + Map data = new HashMap<>(); + data.put("x", point.getBlockX()); + data.put("z", point.getBlockZ()); + points.add(data); + } + + node.setProperty("points", points); + } else if (region instanceof GlobalProtectedRegion) { + node.setProperty("type", "global"); + } else { + node.setProperty("type", region.getClass().getCanonicalName()); + } + + node.setProperty("priority", region.getPriority()); + node.setProperty("flags", getFlagData(region)); + node.setProperty("owners", getDomainData(region.getOwners())); + node.setProperty("members", getDomainData(region.getMembers())); + + ProtectedRegion parent = region.getParent(); + if (parent != null) { + node.setProperty("parent", parent.getId()); + } + } + + config.setHeader(FILE_HEADER); + config.save(); + + //noinspection ResultOfMethodCallIgnored + file.delete(); + if (!tempFile.renameTo(file)) { + throw new StorageException("Failed to rename temporary regions file to " + file.getAbsolutePath()); + } + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException { + throw new DifferenceSaveException("Not supported"); + } + + private DefaultDomain parseDomain(YAMLNode node) { + if (node == null) { + return new DefaultDomain(); + } + + DefaultDomain domain = new DefaultDomain(); + + for (String name : node.getStringList("players", null)) { + if (!name.isEmpty()) { + domain.addPlayer(name); + } + } + + for (String stringId : node.getStringList("unique-ids", null)) { + try { + domain.addPlayer(UUID.fromString(stringId)); + } catch (IllegalArgumentException e) { + log.log(Level.WARNING, "Failed to parse UUID '" + stringId + "'", e); + } + } + + for (String name : node.getStringList("groups", null)) { + if (!name.isEmpty()) { + domain.addGroup(name); + } + } + + return domain; + } + + private Map getFlagData(ProtectedRegion region) { + return FlagUtil.marshal(region.getFlags()); + } + + private void setFlags(FlagRegistry flagRegistry, ProtectedRegion region, YAMLNode flagsData) { + if (flagsData != null) { + region.setFlags(flagRegistry.unmarshal(flagsData.getMap(), true)); + } + } + + private Map getDomainData(DefaultDomain domain) { + Map domainData = new HashMap<>(); + + setDomainData(domainData, "players", domain.getPlayers()); + setDomainData(domainData, "unique-ids", domain.getUniqueIds()); + setDomainData(domainData, "groups", domain.getGroups()); + + return domainData; + } + + private void setDomainData(Map domainData, String key, Set domain) { + if (domain.isEmpty()) { + return; + } + + List list = new ArrayList<>(); + + for (Object str : domain) { + list.add(String.valueOf(str)); + } + + domainData.put(key, list); + } + + /** + * Create a YAML processer instance. + * + * @param file the file + * @return a processor instance + */ + private YAMLProcessor createYamlProcessor(File file) { + checkNotNull(file); + return new YAMLProcessor(file, false, YAMLFormat.COMPACT); + } + + /** + * Dump the given object as YAML for debugging purposes. + * + * @param object the object + * @return the YAML string or an error string if dumping fals + */ + private static String toYamlOutput(Object object) { + try { + return ERROR_DUMP_YAML.dump(object).replaceAll("(?m)^", "\t"); + } catch (Throwable t) { + return ""; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java new file mode 100644 index 000000000..8e89ef1db --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataLoader.java @@ -0,0 +1,338 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Table; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabaseUtils; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.error.YAMLException; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +class DataLoader { + + private static final Logger log = Logger.getLogger(DataLoader.class.getCanonicalName()); + + final Connection conn; + final DataSourceConfig config; + final int worldId; + final FlagRegistry flagRegistry; + + private final Map loaded = new HashMap<>(); + private final Map parentSets = new HashMap<>(); + private final Yaml yaml = SQLRegionDatabase.createYaml(); + + DataLoader(SQLRegionDatabase regionStore, Connection conn, FlagRegistry flagRegistry) throws SQLException { + checkNotNull(regionStore); + + this.conn = conn; + this.config = regionStore.getDataSourceConfig(); + this.worldId = regionStore.getWorldId(); + this.flagRegistry = flagRegistry; + } + + public Set load() throws SQLException { + loadCuboids(); + loadPolygons(); + loadGlobals(); + + loadFlags(); + loadDomainUsers(); + loadDomainGroups(); + + RegionDatabaseUtils.relinkParents(loaded, parentSets); + + return new HashSet<>(loaded.values()); + } + + private void loadCuboids() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT g.min_z, g.min_y, g.min_x, " + + " g.max_z, g.max_y, g.max_x, " + + " r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region_cuboid AS g " + + "LEFT JOIN " + config.getTablePrefix() + "region AS r " + + " ON (g.region_id = r.id AND g.world_id = r.world_id) " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + BlockVector3 pt1 = BlockVector3.at(rs.getInt("min_x"), rs.getInt("min_y"), rs.getInt("min_z")); + BlockVector3 pt2 = BlockVector3.at(rs.getInt("max_x"), rs.getInt("max_y"), rs.getInt("max_z")); + + BlockVector3 min = pt1.getMinimum(pt2); + BlockVector3 max = pt1.getMaximum(pt2); + ProtectedRegion region = new ProtectedCuboidRegion(rs.getString("id"), min, max); + + region.setPriority(rs.getInt("priority")); + + loaded.put(rs.getString("id"), region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadGlobals() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region AS r " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.type = 'global' AND r.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = new GlobalProtectedRegion(rs.getString("id")); + + region.setPriority(rs.getInt("priority")); + + loaded.put(rs.getString("id"), region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadPolygons() throws SQLException { + ListMultimap pointsCache = ArrayListMultimap.create(); + + // First get all the vertices and store them in memory + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT region_id, x, z " + + "FROM " + config.getTablePrefix() + "region_poly2d_point " + + "WHERE world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + pointsCache.put(rs.getString("region_id"), BlockVector2.at(rs.getInt("x"), rs.getInt("z"))); + } + } finally { + closer.closeQuietly(); + } + + // Now we pull the regions themselves + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT g.min_y, g.max_y, r.id, r.priority, p.id AS parent " + + "FROM " + config.getTablePrefix() + "region_poly2d AS g " + + "LEFT JOIN " + config.getTablePrefix() + "region AS r " + + " ON (g.region_id = r.id AND g.world_id = r.world_id) " + + "LEFT JOIN " + config.getTablePrefix() + "region AS p " + + " ON (r.parent = p.id AND r.world_id = p.world_id) " + + "WHERE r.world_id = " + worldId + )); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + String id = rs.getString("id"); + + // Get the points from the cache + List points = pointsCache.get(id); + + if (points.size() < 3) { + log.log(Level.WARNING, "Invalid polygonal region '" + id + "': region has " + points.size() + " point(s) (less than the required 3). Skipping this region."); + continue; + } + + Integer minY = rs.getInt("min_y"); + Integer maxY = rs.getInt("max_y"); + + ProtectedRegion region = new ProtectedPolygonalRegion(id, points, minY, maxY); + region.setPriority(rs.getInt("priority")); + + loaded.put(id, region); + + String parentId = rs.getString("parent"); + if (parentId != null) { + parentSets.put(region, parentId); + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadFlags() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT region_id, flag, value " + + "FROM " + config.getTablePrefix() + "region_flag " + + "WHERE world_id = " + worldId + + " AND region_id IN " + + "(SELECT id FROM " + + config.getTablePrefix() + "region " + + "WHERE world_id = " + worldId + ")")); + + ResultSet rs = closer.register(stmt.executeQuery()); + + Table data = HashBasedTable.create(); + while (rs.next()) { + data.put( + rs.getString("region_id"), + rs.getString("flag"), + unmarshalFlagValue(rs.getString("value"))); + } + + for (Entry> entry : data.rowMap().entrySet()) { + ProtectedRegion region = loaded.get(entry.getKey()); + region.setFlags(flagRegistry.unmarshal(entry.getValue(), true)); + } + } finally { + closer.closeQuietly(); + } + } + + private void loadDomainUsers() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT p.region_id, u.name, u.uuid, p.owner " + + "FROM " + config.getTablePrefix() + "region_players AS p " + + "LEFT JOIN " + config.getTablePrefix() + "user AS u " + + " ON (p.user_id = u.id) " + + "WHERE p.world_id = " + worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = loaded.get(rs.getString("region_id")); + + if (region != null) { + DefaultDomain domain; + + if (rs.getBoolean("owner")) { + domain = region.getOwners(); + } else { + domain = region.getMembers(); + } + + String name = rs.getString("name"); + String uuid = rs.getString("uuid"); + + if (name != null) { + //noinspection deprecation + domain.addPlayer(name); + } else if (uuid != null) { + try { + domain.addPlayer(UUID.fromString(uuid)); + } catch (IllegalArgumentException e) { + log.warning("Invalid UUID '" + uuid + "' for region '" + region.getId() + "'"); + } + } + } + } + } finally { + closer.closeQuietly(); + } + } + + private void loadDomainGroups() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT rg.region_id, g.name, rg.owner " + + "FROM `" + config.getTablePrefix() + "region_groups` AS rg " + + "INNER JOIN `" + config.getTablePrefix() + "group` AS g ON (rg.group_id = g.id) " + + // LEFT JOIN is returning NULLS for reasons unknown + "AND rg.world_id = " + this.worldId)); + + ResultSet rs = closer.register(stmt.executeQuery()); + + while (rs.next()) { + ProtectedRegion region = loaded.get(rs.getString("region_id")); + + if (region != null) { + DefaultDomain domain; + + if (rs.getBoolean("owner")) { + domain = region.getOwners(); + } else { + domain = region.getMembers(); + } + + domain.addGroup(rs.getString("name")); + } + } + } finally { + closer.closeQuietly(); + } + } + + private Object unmarshalFlagValue(String rawValue) { + try { + return yaml.load(rawValue); + } catch (YAMLException e) { + return String.valueOf(rawValue); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java new file mode 100644 index 000000000..99261046b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DataUpdater.java @@ -0,0 +1,168 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import javax.annotation.Nullable; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +class DataUpdater { + + final Connection conn; + final DataSourceConfig config; + final int worldId; + final DomainTableCache domainTableCache; + + DataUpdater(SQLRegionDatabase regionStore, Connection conn) throws SQLException { + checkNotNull(regionStore); + + this.conn = conn; + this.config = regionStore.getDataSourceConfig(); + this.worldId = regionStore.getWorldId(); + this.domainTableCache = new DomainTableCache(config, conn); + } + + /** + * Save the given set of regions to the database. + * + * @param regions a set of regions to save + * @throws SQLException thrown on a fatal SQL error + */ + public void saveAll(Set regions) throws SQLException { + executeSave(regions, null); + } + + /** + * Save the given set of regions to the database. + * + * @param changed a set of changed regions + * @param removed a set of removed regions + * @throws SQLException thrown on a fatal SQL error + */ + public void saveChanges(Set changed, Set removed) throws SQLException { + executeSave(changed, removed); + } + + /** + * Execute the save operation. + * + * @param toUpdate a list of regions to update + * @param toRemove a list of regions to remove, or {@code null} to remove + * regions in the database that were not in {@code toUpdate} + * @throws SQLException thrown on a fatal SQL error + */ + private void executeSave(Set toUpdate, @Nullable Set toRemove) throws SQLException { + Map existing = getExistingRegions(); // Map of regions that already exist in the database + + // WARNING: The database uses utf8_bin for its collation, so + // we have to remove the exact same ID (it is case-sensitive!) + + try { + conn.setAutoCommit(false); + + RegionUpdater updater = new RegionUpdater(this); + RegionInserter inserter = new RegionInserter(this); + RegionRemover remover = new RegionRemover(this); + + for (ProtectedRegion region : toUpdate) { + if (toRemove != null && toRemove.contains(region)) { + continue; + } + + String currentType = existing.get(region.getId()); + + // Check if the region + if (currentType != null) { // Region exists in the database + existing.remove(region.getId()); + + updater.updateRegionType(region); + remover.removeGeometry(region, currentType); + } else { + inserter.insertRegionType(region); + } + + inserter.insertGeometry(region); + updater.updateRegionProperties(region); + } + + if (toRemove != null) { + List removeNames = new ArrayList<>(); + for (ProtectedRegion region : toRemove) { + removeNames.add(region.getId()); + } + remover.removeRegionsEntirely(removeNames); + } else { + remover.removeRegionsEntirely(existing.keySet()); + } + + remover.apply(); + inserter.apply(); + updater.apply(); + + conn.commit(); + } catch (SQLException e) { + conn.rollback(); + throw e; + } catch (RuntimeException e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(true); + } + + // Connection to be closed by caller + } + + private Map getExistingRegions() throws SQLException { + Map existing = new HashMap<>(); + + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT id, type " + + "FROM " + config.getTablePrefix() + "region " + + "WHERE world_id = " + worldId)); + + ResultSet resultSet = closer.register(stmt.executeQuery()); + + while (resultSet.next()) { + existing.put(resultSet.getString("id"), resultSet.getString("type")); + } + + return existing; + } finally { + closer.closeQuietly(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java new file mode 100644 index 000000000..9ac4cf356 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/DomainTableCache.java @@ -0,0 +1,53 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.GroupNameCache; +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.UserNameCache; +import com.sk89q.worldguard.protection.managers.storage.sql.TableCache.UserUuidCache; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; + +class DomainTableCache { + + private final UserNameCache userNameCache; + private final UserUuidCache userUuidCache; + private final GroupNameCache groupNameCache; + + DomainTableCache(DataSourceConfig config, Connection conn) { + userNameCache = new UserNameCache(config, conn); + userUuidCache = new UserUuidCache(config, conn); + groupNameCache = new GroupNameCache(config, conn); + } + + public UserNameCache getUserNameCache() { + return userNameCache; + } + + public UserUuidCache getUserUuidCache() { + return userUuidCache; + } + + public GroupNameCache getGroupNameCache() { + return groupNameCache; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java new file mode 100644 index 000000000..d63c37cf0 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionInserter.java @@ -0,0 +1,189 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.Lists; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * Insert regions that don't exist in the database yet. + */ +class RegionInserter { + + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final List all = new ArrayList<>(); + private final List cuboids = new ArrayList<>(); + private final List polygons = new ArrayList<>(); + + RegionInserter(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + } + + public void insertRegionType(ProtectedRegion region) throws SQLException { + all.add(region); + } + + @SuppressWarnings("StatementWithEmptyBody") + public void insertGeometry(ProtectedRegion region) throws SQLException { + if (region instanceof ProtectedCuboidRegion) { + cuboids.add((ProtectedCuboidRegion) region); + + } else if (region instanceof ProtectedPolygonalRegion) { + polygons.add((ProtectedPolygonalRegion) region); + + } else if (region instanceof GlobalProtectedRegion) { + // Nothing special to do about them + + } else { + throw new IllegalArgumentException("Unknown type of region: " + region.getClass().getName()); + } + } + + private void insertRegionTypes() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region " + + "(id, world_id, type, priority, parent) " + + "VALUES " + + "(?, ?, ?, ?, NULL)")); + + for (List partition : Lists.partition(all, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.setInt(2, worldId); + stmt.setString(3, SQLRegionDatabase.getRegionTypeName(region)); + stmt.setInt(4, region.getPriority()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertCuboids() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_cuboid " + + "(region_id, world_id, min_z, min_y, min_x, max_z, max_y, max_x ) " + + "VALUES " + + "(?, " + worldId + ", ?, ?, ?, ?, ?, ?)")); + + for (List partition : Lists.partition(cuboids, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedCuboidRegion region : partition) { + BlockVector3 min = region.getMinimumPoint(); + BlockVector3 max = region.getMaximumPoint(); + + stmt.setString(1, region.getId()); + stmt.setInt(2, min.getBlockZ()); + stmt.setInt(3, min.getBlockY()); + stmt.setInt(4, min.getBlockX()); + stmt.setInt(5, max.getBlockZ()); + stmt.setInt(6, max.getBlockY()); + stmt.setInt(7, max.getBlockX()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertPolygons() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_poly2d " + + "(region_id, world_id, max_y, min_y) " + + "VALUES " + + "(?, " + worldId + ", ?, ?)")); + + for (List partition : Lists.partition(polygons, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedPolygonalRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.setInt(2, region.getMaximumPoint().getBlockY()); + stmt.setInt(3, region.getMinimumPoint().getBlockY()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void insertPolygonVertices() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_poly2d_point" + + "(region_id, world_id, z, x) " + + "VALUES " + + "(?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedPolygonalRegion region : polygons) { + for (BlockVector2 point : region.getPoints()) { + stmt.setString(1, region.getId()); + stmt.setInt(2, point.getBlockZ()); + stmt.setInt(3, point.getBlockX()); + batch.addBatch(); + } + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + insertRegionTypes(); + insertCuboids(); + insertPolygons(); + insertPolygonVertices(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java new file mode 100644 index 000000000..cdd9c57c5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionRemover.java @@ -0,0 +1,88 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class RegionRemover { + + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final List regionQueue = new ArrayList<>(); + private final List cuboidGeometryQueue = new ArrayList<>(); + private final List polygonGeometryQueue = new ArrayList<>(); + + RegionRemover(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + } + + public void removeRegionsEntirely(Collection names) { + regionQueue.addAll(names); + } + + public void removeGeometry(ProtectedRegion region, String currentType) { + if (currentType.equals("cuboid")) { + cuboidGeometryQueue.add(region.getId()); + } else if (currentType.equals("poly2d")) { + polygonGeometryQueue.add(region.getId()); + } else if (currentType.equals("global")) { + // Nothing to do + } else { + throw new RuntimeException("Unknown type of region in the database: " + currentType); + } + + } + + private void removeRows(Collection names, String table, String field) throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + table + " WHERE " + field + " = ? AND world_id = " + worldId)); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + for (String name : names) { + stmt.setString(1, name); + batch.addBatch(); + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + removeRows(regionQueue, "region", "id"); + removeRows(cuboidGeometryQueue, "region_cuboid", "region_id"); + removeRows(polygonGeometryQueue, "region_poly2d", "region_id"); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java new file mode 100644 index 000000000..22e73f924 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/RegionUpdater.java @@ -0,0 +1,341 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.Lists; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.yaml.snakeyaml.Yaml; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Updates region data that needs to be updated for both inserts and updates. + */ +class RegionUpdater { + + private static final Logger log = Logger.getLogger(RegionUpdater.class.getCanonicalName()); + private final DataSourceConfig config; + private final Connection conn; + private final int worldId; + private final DomainTableCache domainTableCache; + + private final Set userNames = new HashSet<>(); + private final Set userUuids = new HashSet<>(); + private final Set groupNames = new HashSet<>(); + + private final Yaml yaml = SQLRegionDatabase.createYaml(); + + private final List typesToUpdate = new ArrayList<>(); + private final List parentsToSet = new ArrayList<>(); + private final List flagsToReplace = new ArrayList<>(); + private final List domainsToReplace = new ArrayList<>(); + + RegionUpdater(DataUpdater updater) { + this.config = updater.config; + this.conn = updater.conn; + this.worldId = updater.worldId; + this.domainTableCache = updater.domainTableCache; + } + + public void updateRegionType(ProtectedRegion region) { + typesToUpdate.add(region); + } + + public void updateRegionProperties(ProtectedRegion region) { + if (region.getParent() != null) { + parentsToSet.add(region); + } + + flagsToReplace.add(region); + domainsToReplace.add(region); + + addDomain(region.getOwners()); + addDomain(region.getMembers()); + } + + private void addDomain(DefaultDomain domain) { + //noinspection deprecation + for (String name : domain.getPlayers()) { + userNames.add(name.toLowerCase()); + } + + for (UUID uuid : domain.getUniqueIds()) { + userUuids.add(uuid); + } + + for (String name : domain.getGroups()) { + groupNames.add(name.toLowerCase()); + } + } + + private void setParents() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "UPDATE " + config.getTablePrefix() + "region " + + "SET parent = ? " + + "WHERE id = ? AND world_id = " + worldId)); + + for (List partition : Lists.partition(parentsToSet, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + ProtectedRegion parent = region.getParent(); + if (parent != null) { // Parent would be null due to a race condition + stmt.setString(1, parent.getId()); + stmt.setString(2, region.getId()); + stmt.addBatch(); + } + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + private void replaceFlags() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_flag " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(flagsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_flag " + + "(id, region_id, world_id, flag, value) " + + "VALUES " + + "(null, ?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : flagsToReplace) { + for (Map.Entry, Object> entry : region.getFlags().entrySet()) { + if (entry.getValue() == null) continue; + + Object flag = marshalFlagValue(entry.getKey(), entry.getValue()); + + stmt.setString(1, region.getId()); + stmt.setString(2, entry.getKey().getName()); + stmt.setObject(3, flag); + batch.addBatch(); + } + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void replaceDomainUsers() throws SQLException { + // Remove users + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_players " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(domainsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + // Add users + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_players " + + "(region_id, world_id, user_id, owner) " + + "VALUES (?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : domainsToReplace) { + insertDomainUsers(stmt, batch, region, region.getMembers(), false); // owner = false + insertDomainUsers(stmt, batch, region, region.getOwners(), true); // owner = true + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void insertDomainUsers(PreparedStatement stmt, StatementBatch batch, ProtectedRegion region, DefaultDomain domain, boolean owner) throws SQLException { + //noinspection deprecation + for (String name : domain.getPlayers()) { + Integer id = domainTableCache.getUserNameCache().find(name); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the user identified as '" + name + "'"); + } + } + + for (UUID uuid : domain.getUniqueIds()) { + Integer id = domainTableCache.getUserUuidCache().find(uuid); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the user identified by '" + uuid + "'"); + } + } + } + + private void replaceDomainGroups() throws SQLException { + // Remove groups + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "DELETE FROM " + config.getTablePrefix() + "region_groups " + + "WHERE region_id = ? " + + "AND world_id = " + worldId)); + + for (List partition : Lists.partition(domainsToReplace, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + + // Add groups + closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "region_groups " + + "(region_id, world_id, group_id, owner) " + + "VALUES (?, " + worldId + ", ?, ?)")); + + StatementBatch batch = new StatementBatch(stmt, StatementBatch.MAX_BATCH_SIZE); + + for (ProtectedRegion region : domainsToReplace) { + insertDomainGroups(stmt, batch, region, region.getMembers(), false); // owner = false + insertDomainGroups(stmt, batch, region, region.getOwners(), true); // owner = true + } + + batch.executeRemaining(); + } finally { + closer.closeQuietly(); + } + } + + private void insertDomainGroups(PreparedStatement stmt, StatementBatch batch, ProtectedRegion region, DefaultDomain domain, boolean owner) throws SQLException { + for (String name : domain.getGroups()) { + Integer id = domainTableCache.getGroupNameCache().find(name); + if (id != null) { + stmt.setString(1, region.getId()); + stmt.setInt(2, id); + stmt.setBoolean(3, owner); + batch.addBatch(); + } else { + log.log(Level.WARNING, "Did not find an ID for the group identified as '" + name + "'"); + } + } + } + + private void updateRegionTypes() throws SQLException { + Closer closer = Closer.create(); + try { + PreparedStatement stmt = closer.register(conn.prepareStatement( + "UPDATE " + config.getTablePrefix() + "region " + + "SET type = ?, priority = ?, parent = NULL " + + "WHERE id = ? AND world_id = " + worldId)); + + for (List partition : Lists.partition(typesToUpdate, StatementBatch.MAX_BATCH_SIZE)) { + for (ProtectedRegion region : partition) { + stmt.setString(1, SQLRegionDatabase.getRegionTypeName(region)); + stmt.setInt(2, region.getPriority()); + stmt.setString(3, region.getId()); + stmt.addBatch(); + } + + stmt.executeBatch(); + } + } finally { + closer.closeQuietly(); + } + } + + public void apply() throws SQLException { + domainTableCache.getUserNameCache().fetch(userNames); + domainTableCache.getUserUuidCache().fetch(userUuids); + domainTableCache.getGroupNameCache().fetch(groupNames); + + updateRegionTypes(); + setParents(); + replaceFlags(); + replaceDomainUsers(); + replaceDomainGroups(); + } + + @SuppressWarnings("unchecked") + private Object marshalFlagValue(Flag flag, Object val) { + return yaml.dump(flag.marshal((V) val)); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLDriver.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLDriver.java new file mode 100644 index 000000000..628886c54 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLDriver.java @@ -0,0 +1,257 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.FlywayException; +import org.flywaydb.core.api.MigrationVersion; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores regions using a JDBC connection with support for SQL. + * + *

Note, however, that this implementation only supports MySQL. + *

+ */ +public class SQLDriver implements RegionDriver { + + private static final Logger log = Logger.getLogger(SQLDriver.class.getCanonicalName()); + private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(); + private static final int CONNECTION_TIMEOUT = 6000; + + private final DataSourceConfig config; + private boolean initialized = false; + + /** + * Create a new instance. + * + * @param config a configuration + */ + public SQLDriver(DataSourceConfig config) { + checkNotNull(config); + this.config = config; + } + + @Override + public RegionDatabase get(String name) { + return new SQLRegionDatabase(this, name); + } + + @Override + public List getAll() throws StorageException { + Closer closer = Closer.create(); + try { + List stores = new ArrayList<>(); + Connection connection = closer.register(getConnection()); + Statement stmt = connection.createStatement(); + ResultSet rs = closer.register(stmt.executeQuery("SELECT name FROM " + config.getTablePrefix() + "world")); + while (rs.next()) { + stores.add(get(rs.getString(1))); + } + return stores; + } catch (SQLException e) { + throw new StorageException("Failed to fetch list of worlds", e); + } finally { + closer.closeQuietly(); + } + } + + /** + * Perform initialization if it hasn't been (successfully) performed yet. + * + * @throws StorageException thrown on error + */ + synchronized void initialize() throws StorageException { + if (!initialized) { + try { + migrate(); + } catch (SQLException e) { + throw new StorageException("Failed to migrate database tables", e); + } + initialized = true; + } + } + + /** + * Attempt to migrate the tables to the latest version. + * + * @throws StorageException thrown if migration fails + * @throws SQLException thrown on SQL error + */ + private void migrate() throws SQLException, StorageException { + Closer closer = Closer.create(); + Connection conn = closer.register(getConnection()); + + try { + // Check some tables + boolean tablesExist; + boolean isRecent; + boolean isBeforeMigrations; + boolean hasMigrations; + + try { + tablesExist = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1"); + isRecent = tryQuery(conn, "SELECT world_id FROM " + config.getTablePrefix() + "region_cuboid LIMIT 1"); + isBeforeMigrations = !tryQuery(conn, "SELECT uuid FROM " + config.getTablePrefix() + "user LIMIT 1"); + hasMigrations = tryQuery(conn, "SELECT * FROM " + config.getTablePrefix() + "migrations LIMIT 1"); + } finally { + closer.closeQuietly(); + } + + // We don't bother with migrating really old tables + if (tablesExist && !isRecent) { + throw new StorageException( + "Sorry, your tables are too old for the region SQL auto-migration system. " + + "Please run region_manual_update_20110325.sql on your database, which comes " + + "with WorldGuard or can be found in http://github.com/sk89q/worldguard"); + } + + // Our placeholders + Map placeHolders = new HashMap<>(); + placeHolders.put("tablePrefix", config.getTablePrefix()); + + Flyway flyway = new Flyway(); + + // The SQL support predates the usage of Flyway, so let's do some + // checks and issue messages appropriately + if (!hasMigrations) { + flyway.setInitOnMigrate(true); + + if (tablesExist) { + // Detect if this is before migrations + if (isBeforeMigrations) { + flyway.setInitVersion(MigrationVersion.fromVersion("1")); + } + + log.log(Level.INFO, "The SQL region tables exist but the migrations table seems to not exist yet. Creating the migrations table..."); + } else { + // By default, if Flyway sees any tables at all in the schema, it + // will assume that we are up to date, so we have to manually + // check ourselves and then ask Flyway to start from the beginning + // if our test table doesn't exist + flyway.setInitVersion(MigrationVersion.fromVersion("0")); + + log.log(Level.INFO, "SQL region tables do not exist: creating..."); + } + } + + flyway.setClassLoader(getClass().getClassLoader()); + flyway.setLocations("migrations/region/" + getMigrationFolderName()); + flyway.setDataSource(config.getDsn(), config.getUsername(), config.getPassword()); + flyway.setTable(config.getTablePrefix() + "migrations"); + flyway.setPlaceholders(placeHolders); + flyway.setValidateOnMigrate(false); + flyway.migrate(); + } catch (FlywayException e) { + throw new StorageException("Failed to migrate tables", e); + } finally { + closer.closeQuietly(); + } + } + + /** + * Get the name of the folder in migrations/region containing the migration files. + * + * @return the migration folder name + */ + public String getMigrationFolderName() { + return "mysql"; + } + + /** + * Try to execute a query and return true if it did not fail. + * + * @param conn the connection to run the query on + * @param sql the SQL query + * @return true if the query did not end in error + */ + private boolean tryQuery(Connection conn, String sql) { + Closer closer = Closer.create(); + try { + Statement statement = closer.register(conn.createStatement()); + statement.executeQuery(sql); + return true; + } catch (SQLException ex) { + return false; + } finally { + closer.closeQuietly(); + } + } + + /** + * Get the database configuration. + * + * @return the database configuration + */ + DataSourceConfig getConfig() { + return config; + } + + /** + * Create a new connection. + * + * @return the connection + * @throws SQLException raised if the connection cannot be instantiated + */ + Connection getConnection() throws SQLException { + Future future = EXECUTOR.submit(new Callable() { + @Override + public Connection call() throws Exception { + return config.getConnection(); + } + }); + + try { + return future.get(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new SQLException("Failed to get a SQL connection because the operation was interrupted", e); + } catch (ExecutionException e) { + throw new SQLException("Failed to get a SQL connection due to an error", e); + } catch (TimeoutException e) { + future.cancel(true); + throw new SQLException("Failed to get a SQL connection within the time limit"); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionDatabase.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionDatabase.java new file mode 100644 index 000000000..b6b99b0d5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/SQLRegionDatabase.java @@ -0,0 +1,277 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionDifference; +import com.sk89q.worldguard.protection.managers.storage.DifferenceSaveException; +import com.sk89q.worldguard.protection.managers.storage.RegionDatabase; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; +import org.yaml.snakeyaml.representer.Representer; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores region data into a SQL database in a highly normalized fashion. + */ +class SQLRegionDatabase implements RegionDatabase { + + private final String worldName; + private final DataSourceConfig config; + private final SQLDriver driver; + private int worldId; + private boolean initialized = false; + + /** + * Create a new instance. + * + * @param driver the driver instance + * @param worldName the name of the world to store regions by + */ + SQLRegionDatabase(SQLDriver driver, String worldName) { + checkNotNull(driver); + checkNotNull(worldName); + + this.config = driver.getConfig(); + this.worldName = worldName; + this.driver = driver; + } + + @Override + public String getName() { + return worldName; + } + + /** + * Initialize the database if it hasn't been yet initialized. + * + * @throws StorageException thrown if initialization fails + */ + private synchronized void initialize() throws StorageException { + if (!initialized) { + driver.initialize(); + + try { + worldId = chooseWorldId(worldName); + } catch (SQLException e) { + throw new StorageException("Failed to choose the ID for this world", e); + } + + initialized = true; + } + } + + /** + * Get the ID for this world from the database or pick a new one if + * an entry does not exist yet. + * + * @param worldName the world name + * @return a world ID + * @throws SQLException on a database access error + */ + private int chooseWorldId(String worldName) throws SQLException { + Closer closer = Closer.create(); + try { + Connection conn = closer.register(getConnection()); + + PreparedStatement stmt = closer.register(conn.prepareStatement( + "SELECT id FROM " + config.getTablePrefix() + "world WHERE name = ? LIMIT 0, 1")); + + stmt.setString(1, worldName); + ResultSet worldResult = closer.register(stmt.executeQuery()); + + if (worldResult.next()) { + return worldResult.getInt("id"); + } else { + PreparedStatement stmt2 = closer.register(conn.prepareStatement( + "INSERT INTO " + config.getTablePrefix() + "world (id, name) VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS)); + + stmt2.setString(1, worldName); + stmt2.execute(); + ResultSet generatedKeys = stmt2.getGeneratedKeys(); + + if (generatedKeys.next()) { + return generatedKeys.getInt(1); + } else { + throw new SQLException("Expected result, got none"); + } + } + } finally { + closer.closeQuietly(); + } + } + + /** + * Return a new database connection. + * + * @return a connection + * @throws SQLException thrown if the connection could not be created + */ + private Connection getConnection() throws SQLException { + return driver.getConnection(); + } + + /** + * Get the data source config. + * + * @return the data source config + */ + public DataSourceConfig getDataSourceConfig() { + return config; + } + + /** + * Get the world ID. + * + * @return the world ID + */ + public int getWorldId() { + return worldId; + } + + /** + * Get the identifier string for a region's type. + * + * @param region the region + * @return the ID of the region type + */ + static String getRegionTypeName(ProtectedRegion region) { + if (region instanceof ProtectedCuboidRegion) { + return "cuboid"; + } else if (region instanceof ProtectedPolygonalRegion) { + return "poly2d"; // Differs from getTypeName() on ProtectedRegion + } else if (region instanceof GlobalProtectedRegion) { + return "global"; + } else { + throw new IllegalArgumentException("Unexpected region type: " + region.getClass().getName()); + } + } + + /** + * Create a YAML dumper / parser. + * + * @return a YAML dumper / parser + */ + static Yaml createYaml() { + DumperOptions options = new DumperOptions(); + options.setIndent(2); + options.setDefaultFlowStyle(FlowStyle.FLOW); + Representer representer = new Representer(); + representer.setDefaultFlowStyle(FlowStyle.FLOW); + + // We have to use this in order to properly save non-string values + return new Yaml(new SafeConstructor(), new Representer(), options); + } + + @Override + public Set loadAll(FlagRegistry flagRegistry) throws StorageException { + initialize(); + + Closer closer = Closer.create(); + DataLoader loader; + + try { + try { + loader = new DataLoader(this, closer.register(getConnection()), flagRegistry); + } catch (SQLException e) { + throw new StorageException("Failed to get a connection to the database", e); + } + + try { + return loader.load(); + } catch (SQLException e) { + throw new StorageException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } + + @Override + public void saveAll(Set regions) throws StorageException { + checkNotNull(regions); + + initialize(); + + Closer closer = Closer.create(); + DataUpdater updater; + + try { + try { + updater = new DataUpdater(this, closer.register(getConnection())); + } catch (SQLException e) { + throw new StorageException("Failed to get a connection to the database", e); + } + + try { + updater.saveAll(regions); + } catch (SQLException e) { + throw new StorageException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } + + @Override + public void saveChanges(RegionDifference difference) throws DifferenceSaveException, StorageException { + checkNotNull(difference); + + initialize(); + + Closer closer = Closer.create(); + DataUpdater updater; + + try { + try { + updater = new DataUpdater(this, closer.register(getConnection())); + } catch (SQLException e) { + throw new StorageException("Failed to get a connection to the database", e); + } + + try { + updater.saveChanges(difference.getChanged(), difference.getRemoved()); + } catch (SQLException e) { + throw new StorageException("Failed to save the region data to the database", e); + } + } finally { + closer.closeQuietly(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java new file mode 100644 index 000000000..7a43c3cd8 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/StatementBatch.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +class StatementBatch { + + public static final int MAX_BATCH_SIZE = 100; + + private final PreparedStatement stmt; + private final int batchSize; + private int count = 0; + + StatementBatch(PreparedStatement stmt, int batchSize) { + this.stmt = stmt; + this.batchSize = batchSize; + } + + public void addBatch() throws SQLException { + stmt.addBatch(); + count++; + if (count > batchSize) { + stmt.executeBatch(); + count = 0; + } + } + + public void executeRemaining() throws SQLException { + if (count > 0) { + count = 0; + stmt.executeBatch(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java new file mode 100644 index 000000000..75dda95bd --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/managers/storage/sql/TableCache.java @@ -0,0 +1,262 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.managers.storage.sql; + +import com.google.common.collect.Lists; +import com.sk89q.worldguard.internal.util.sql.StatementUtils; +import com.sk89q.worldguard.util.io.Closer; +import com.sk89q.worldguard.util.sql.DataSourceConfig; + +import javax.annotation.Nullable; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores a cache of entries from a table for fast lookup and + * creates new rows whenever required. + * + * @param the type of entry + */ +abstract class TableCache { + + private static final Logger log = Logger.getLogger(TableCache.class.getCanonicalName()); + + private static final int MAX_NUMBER_PER_QUERY = 100; + private static final Object LOCK = new Object(); + + private final Map cache = new HashMap<>(); + private final DataSourceConfig config; + private final Connection conn; + private final String tableName; + private final String fieldName; + + /** + * Create a new instance. + * + * @param config the data source config + * @param conn the connection to use + * @param tableName the table name + * @param fieldName the field name + */ + protected TableCache(DataSourceConfig config, Connection conn, String tableName, String fieldName) { + this.config = config; + this.conn = conn; + this.tableName = tableName; + this.fieldName = fieldName; + } + + /** + * Convert from the type to the string representation. + * + * @param o the object + * @return the string representation + */ + protected abstract String fromType(V o); + + /** + * Convert from the string representation to the type. + * + * @param o the string + * @return the object + */ + protected abstract V toType(String o); + + /** + * Convert the object to the version that is stored as a key in the map. + * + * @param object the object + * @return the key version + */ + protected abstract V toKey(V object); + + @Nullable + public Integer find(V object) { + return cache.get(object); + } + + /** + * Fetch from the database rows that match the given entries, otherwise + * create new entries and assign them an ID. + * + * @param entries a list of entries + * @throws SQLException thrown on SQL error + */ + public void fetch(Collection entries) throws SQLException { + synchronized (LOCK) { // Lock across all cache instances + checkNotNull(entries); + + // Get a list of missing entries + List fetchList = new ArrayList<>(); + for (V entry : entries) { + if (!cache.containsKey(toKey(entry))) { + fetchList.add(entry); + } + } + + if (fetchList.isEmpty()) { + return; // Nothing to do + } + + // Search for entries + for (List partition : Lists.partition(fetchList, MAX_NUMBER_PER_QUERY)) { + Closer closer = Closer.create(); + try { + PreparedStatement statement = closer.register(conn.prepareStatement( + String.format( + "SELECT id, " + fieldName + " " + + "FROM `" + config.getTablePrefix() + tableName + "` " + + "WHERE " + fieldName + " IN (%s)", + StatementUtils.preparePlaceHolders(partition.size())))); + + String[] values = new String[partition.size()]; + int i = 0; + for (V entry : partition) { + values[i] = fromType(entry); + i++; + } + + StatementUtils.setValues(statement, values); + ResultSet results = closer.register(statement.executeQuery()); + while (results.next()) { + cache.put(toKey(toType(results.getString(fieldName))), results.getInt("id")); + } + } finally { + closer.closeQuietly(); + } + } + + List missing = new ArrayList<>(); + for (V entry : fetchList) { + if (!cache.containsKey(toKey(entry))) { + missing.add(entry); + } + } + + // Insert entries that are missing + if (!missing.isEmpty()) { + Closer closer = Closer.create(); + try { + PreparedStatement statement = closer.register(conn.prepareStatement( + "INSERT INTO `" + config.getTablePrefix() + tableName + "` (id, " + fieldName + ") VALUES (null, ?)", + Statement.RETURN_GENERATED_KEYS)); + + for (V entry : missing) { + statement.setString(1, fromType(entry)); + statement.execute(); + + ResultSet generatedKeys = statement.getGeneratedKeys(); + if (generatedKeys.next()) { + cache.put(toKey(entry), generatedKeys.getInt(1)); + } else { + log.warning("Could not get the database ID for entry " + entry); + } + } + } finally { + closer.closeQuietly(); + } + } + } + } + + /** + * An index of user rows that utilize the name field. + */ + static class UserNameCache extends TableCache { + protected UserNameCache(DataSourceConfig config, Connection conn) { + super(config, conn, "user", "name"); + } + + @Override + protected String fromType(String o) { + return o; + } + + @Override + protected String toType(String o) { + return o; + } + + @Override + protected String toKey(String object) { + return object.toLowerCase(); + } + } + + /** + * An index of user rows that utilize the UUID field. + */ + static class UserUuidCache extends TableCache { + protected UserUuidCache(DataSourceConfig config, Connection conn) { + super(config, conn, "user", "uuid"); + } + + @Override + protected String fromType(UUID o) { + return o.toString(); + } + + @Override + protected UUID toType(String o) { + return UUID.fromString(o); + } + + @Override + protected UUID toKey(UUID object) { + return object; + } + } + + /** + * An index of group rows. + */ + static class GroupNameCache extends TableCache { + protected GroupNameCache(DataSourceConfig config, Connection conn) { + super(config, conn, "group", "name"); + } + + @Override + protected String fromType(String o) { + return o; + } + + @Override + protected String toType(String o) { + return o; + } + + @Override + protected String toKey(String object) { + return object.toLowerCase(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java new file mode 100644 index 000000000..bdbc54d0e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/GlobalProtectedRegion.java @@ -0,0 +1,103 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; + +import java.awt.geom.Area; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * A special region that is not quite "anywhere" (its volume is 0, it + * contains no positions, and it does not intersect with any other region). + * + *

Global regions, however, are used to specify a region with flags that + * are applied with the lowest priority.

+ */ +public class GlobalProtectedRegion extends ProtectedRegion { + + /** + * Create a new instance.
+ * Equivalent to {@link #GlobalProtectedRegion(String, boolean) GlobalProtectedRegion(id, false)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the ID + */ + public GlobalProtectedRegion(String id) { + this(id, false); + } + + /** + * Create a new instance. + * + * @param id the ID + * @param transientRegion whether this region should only be kept in memory and not be saved + */ + public GlobalProtectedRegion(String id, boolean transientRegion) { + super(id, transientRegion); + min = BlockVector3.ZERO; + max = BlockVector3.ZERO; + } + + @Override + public boolean isPhysicalArea() { + return false; + } + + @Override + public List getPoints() { + // This doesn't make sense + List pts = new ArrayList<>(); + pts.add(BlockVector2.at(min.getBlockX(), min.getBlockZ())); + return pts; + } + + @Override + public int volume() { + return 0; + } + + @Override + public boolean contains(BlockVector3 pt) { + // Global regions are handled separately so it must not contain any positions + return false; + } + + @Override + public RegionType getType() { + return RegionType.GLOBAL; + } + + @Override + public List getIntersectingRegions(Collection regions) { + // Global regions are handled separately so it must not contain any positions + return Collections.emptyList(); + } + + @Override + Area toArea() { + return null; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java new file mode 100644 index 000000000..95b080ca9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedCuboidRegion.java @@ -0,0 +1,181 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.util.MathUtils; + +import java.awt.Rectangle; +import java.awt.geom.Area; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a cuboid region that can be protected. + * + * @author sk89q + */ +public class ProtectedCuboidRegion extends ProtectedRegion { + + /** + * Construct a new instance of this cuboid region.
+ * Equivalent to {@link #ProtectedCuboidRegion(String, boolean, BlockVector3, BlockVector3) + * ProtectedCuboidRegion(id, false, pt1, pt2)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the region id + * @param pt1 the first point of this region + * @param pt2 the second point of this region + */ + public ProtectedCuboidRegion(String id, BlockVector3 pt1, BlockVector3 pt2) { + this(id, false, pt1, pt2); + } + + /** + * Construct a new instance of this cuboid region. + * + * @param id the region id + * @param transientRegion whether this region should only be kept in memory and not be saved + * @param pt1 the first point of this region + * @param pt2 the second point of this region + */ + public ProtectedCuboidRegion(String id, boolean transientRegion, BlockVector3 pt1, BlockVector3 pt2) { + super(id, transientRegion); + setMinMaxPoints(pt1, pt2); + } + + /** + * Given any two points, sets the minimum and maximum points. + * + * @param position1 the first point of this region + * @param position2 the second point of this region + */ + private void setMinMaxPoints(BlockVector3 position1, BlockVector3 position2) { + checkNotNull(position1); + checkNotNull(position2); + + List points = new ArrayList<>(); + points.add(position1); + points.add(position2); + setMinMaxPoints(points); + } + + /** + * Set the lower point of the cuboid. + * + * @param position the point to set as the minimum point + * @deprecated ProtectedRegion bounds should never be mutated. Regions must be redefined to move them. + * This method will be removed in a future release. + */ + @Deprecated + public void setMinimumPoint(BlockVector3 position) { + WorldGuard.logger.warning("ProtectedCuboidRegion#setMinimumPoint call ignored. Mutating regions leads to undefined behavior."); + } + + /** + * Set the upper point of the cuboid. + * + * @param position the point to set as the maximum point + * @deprecated ProtectedRegion bounds should never be mutated. Regions must be redefined to move them. + * This method will be removed in a future release. + */ + @Deprecated + public void setMaximumPoint(BlockVector3 position) { + WorldGuard.logger.warning("ProtectedCuboidRegion#setMaximumPoint call ignored. Mutating regions leads to undefined behavior."); + } + + @Override + public boolean isPhysicalArea() { + return true; + } + + @Override + public List getPoints() { + List pts = new ArrayList<>(); + int x1 = min.getBlockX(); + int x2 = max.getBlockX(); + int z1 = min.getBlockZ(); + int z2 = max.getBlockZ(); + + pts.add(BlockVector2.at(x1, z1)); + pts.add(BlockVector2.at(x2, z1)); + pts.add(BlockVector2.at(x2, z2)); + pts.add(BlockVector2.at(x1, z2)); + + return pts; + } + + @Override + public boolean contains(BlockVector3 pt) { + final double x = pt.getX(); + final double y = pt.getY(); + final double z = pt.getZ(); + return x >= min.getBlockX() && x < max.getBlockX() + 1 + && y >= min.getBlockY() && y < max.getBlockY() + 1 + && z >= min.getBlockZ() && z < max.getBlockZ() + 1; + } + + @Override + public RegionType getType() { + return RegionType.CUBOID; + } + + @Override + Area toArea() { + int x = getMinimumPoint().getBlockX(); + int z = getMinimumPoint().getBlockZ(); + int width = getMaximumPoint().getBlockX() - x + 1; + int height = getMaximumPoint().getBlockZ() - z + 1; + return new Area(new Rectangle(x, z, width, height)); + } + + @Override + protected boolean intersects(ProtectedRegion region, Area thisArea) { + if (region instanceof ProtectedCuboidRegion) { + return intersectsBoundingBox(region); + } else { + return super.intersects(region, thisArea); + } + } + + @Override + public int volume() { + int xLength = max.getBlockX() - min.getBlockX() + 1; + int yLength = max.getBlockY() - min.getBlockY() + 1; + int zLength = max.getBlockZ() - min.getBlockZ() + 1; + + try { + long v = MathUtils.checkedMultiply(xLength, yLength); + v = MathUtils.checkedMultiply(v, zLength); + if (v > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) v; + } + } catch (ArithmeticException e) { + return Integer.MAX_VALUE; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java new file mode 100644 index 000000000..7e8defa03 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedPolygonalRegion.java @@ -0,0 +1,191 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; + +import java.awt.Polygon; +import java.awt.geom.Area; +import java.util.ArrayList; +import java.util.List; + +public class ProtectedPolygonalRegion extends ProtectedRegion { + + private final ImmutableList points; + private final int minY; + private final int maxY; + + /** + * Construct a new instance of this polygonal region.
+ * Equivalent to {@link #ProtectedPolygonalRegion(String, boolean, List, int, int) + * ProtectedPolygonalRegion(id, false, points, minY, maxY)}
+ * transientRegion will be set to false, and this region can be saved. + * + * @param id the region id + * @param points a {@link List} of points that this region should contain + * @param minY the minimum y coordinate + * @param maxY the maximum y coordinate + */ + public ProtectedPolygonalRegion(String id, List points, int minY, int maxY) { + this(id, false, points, minY, maxY); + } + + /** + * Construct a new instance of this polygonal region. + * + * @param id the region id + * @param transientRegion whether this region should only be kept in memory and not be saved + * @param points a {@link List} of points that this region should contain + * @param minY the minimum y coordinate + * @param maxY the maximum y coordinate + */ + public ProtectedPolygonalRegion(String id, boolean transientRegion, List points, int minY, int maxY) { + super(id, transientRegion); + ImmutableList immutablePoints = ImmutableList.copyOf(points); + setMinMaxPoints(immutablePoints, minY, maxY); + this.points = immutablePoints; + this.minY = min.getBlockY(); + this.maxY = max.getBlockY(); + } + + /** + * Sets the min and max points from all the 2d points and the min/max Y values + * + * @param points2D A {@link List} of points that this region should contain + * @param minY The minimum y coordinate + * @param maxY The maximum y coordinate + */ + private void setMinMaxPoints(List points2D, int minY, int maxY) { + checkNotNull(points2D); + + List points = new ArrayList<>(); + int y = minY; + for (BlockVector2 point2D : points2D) { + points.add(BlockVector3.at(point2D.getBlockX(), y, point2D.getBlockZ())); + y = maxY; + } + setMinMaxPoints(points); + } + + @Override + public boolean isPhysicalArea() { + return true; + } + + @Override + public List getPoints() { + return points; + } + + @Override + public boolean contains(BlockVector3 position) { + checkNotNull(position); + + int targetX = position.getBlockX(); // Width + int targetY = position.getBlockY(); // Height + int targetZ = position.getBlockZ(); // Depth + + if (targetY < minY || targetY > maxY) { + return false; + } + //Quick and dirty check. + if (targetX < min.getBlockX() || targetX > max.getBlockX() || targetZ < min.getBlockZ() || targetZ > max.getBlockZ()) { + return false; + } + boolean inside = false; + int npoints = points.size(); + int xNew, zNew; + int xOld, zOld; + int x1, z1; + int x2, z2; + long crossproduct; + int i; + + xOld = points.get(npoints - 1).getBlockX(); + zOld = points.get(npoints - 1).getBlockZ(); + + for (i = 0; i < npoints; i++) { + xNew = points.get(i).getBlockX(); + zNew = points.get(i).getBlockZ(); + //Check for corner + if (xNew == targetX && zNew == targetZ) { + return true; + } + if (xNew > xOld) { + x1 = xOld; + x2 = xNew; + z1 = zOld; + z2 = zNew; + } else { + x1 = xNew; + x2 = xOld; + z1 = zNew; + z2 = zOld; + } + if (x1 <= targetX && targetX <= x2) { + crossproduct = ((long) targetZ - (long) z1) * (long) (x2 - x1) + - ((long) z2 - (long) z1) * (long) (targetX - x1); + if (crossproduct == 0) { + if ((z1 <= targetZ) == (targetZ <= z2)) return true; // on edge + } else if (crossproduct < 0 && (x1 != targetX)) { + inside = !inside; + } + } + xOld = xNew; + zOld = zNew; + } + + return inside; + } + + @Override + public RegionType getType() { + return RegionType.POLYGON; + } + + @Override + Area toArea() { + List points = getPoints(); + int numPoints = points.size(); + int[] xCoords = new int[numPoints]; + int[] yCoords = new int[numPoints]; + + int i = 0; + for (BlockVector2 point : points) { + xCoords[i] = point.getBlockX(); + yCoords[i] = point.getBlockZ(); + i++; + } + + Polygon polygon = new Polygon(xCoords, yCoords, numPoints); + return new Area(polygon); + } + + @Override + public int volume() { + // TODO: Fix this -- the previous algorithm returned incorrect results, but the current state of this method is even worse + return 0; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java new file mode 100644 index 000000000..76a527ccc --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegion.java @@ -0,0 +1,753 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Lists; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.util.ChangeTracked; +import com.sk89q.worldguard.util.Normal; + +import java.awt.geom.Area; +import java.awt.geom.Line2D; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +/** + * Represents a region that can be indexed and have spatial queries performed + * against it. + * + *

Instances can be modified and access from several threads at a time.

+ * + * Note: this class has a natural ordering that is inconsistent with equals. + * Regions with identical ids (and also the same priority) may exist in different managers (or no manager at all), + * so care should be taken when comparing regions that have not been obtained from a single manager. + */ +public abstract class ProtectedRegion implements ChangeTracked, Comparable { + + public static final String GLOBAL_REGION = "__global__"; + private static final Pattern VALID_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_,'\\-\\+/]{1,}$"); + + protected BlockVector3 min; + protected BlockVector3 max; + + private final String id; + private final boolean transientRegion; + private int priority = 0; + private ProtectedRegion parent; + private DefaultDomain owners = new DefaultDomain(); + private DefaultDomain members = new DefaultDomain(); + private ConcurrentMap, Object> flags = new ConcurrentHashMap<>(); + private boolean dirty = true; + + /** + * Construct a new instance of this region. + * + * @param id the name of this region + * @param transientRegion whether this region should only be kept in memory and not be saved + * @throws IllegalArgumentException thrown if the ID is invalid (see {@link #isValidId(String)} + */ + ProtectedRegion(String id, boolean transientRegion) { // Package private because we can't have people creating their own region types + checkNotNull(id); + + if (!isValidId(id)) { + throw new IllegalArgumentException("Invalid region ID: " + id); + } + + this.id = Normal.normalize(id); + this.transientRegion = transientRegion; + } + + /** + * Set the minimum and maximum points of the bounding box for a region + * + * @param points the points to set with at least one entry + */ + protected void setMinMaxPoints(List points) { + int minX = points.get(0).getBlockX(); + int minY = points.get(0).getBlockY(); + int minZ = points.get(0).getBlockZ(); + int maxX = minX; + int maxY = minY; + int maxZ = minZ; + + for (BlockVector3 v : points) { + int x = v.getBlockX(); + int y = v.getBlockY(); + int z = v.getBlockZ(); + + if (x < minX) minX = x; + if (y < minY) minY = y; + if (z < minZ) minZ = z; + + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + if (z > maxZ) maxZ = z; + } + + setDirty(true); + min = BlockVector3.at(minX, minY, minZ); + max = BlockVector3.at(maxX, maxY, maxZ); + } + + /** + * Gets the name of this region + * + * @return the name + */ + public String getId() { + return id; + } + + /** + * Return whether this type of region encompasses physical area. + * + * @return Whether physical area is encompassed + */ + public abstract boolean isPhysicalArea(); + + /** + * Get a vector containing the smallest X, Y, and Z components for the + * corner of the axis-aligned bounding box that contains this region. + * + * @return the minimum point + */ + public BlockVector3 getMinimumPoint() { + return min; + } + + /** + * Get a vector containing the highest X, Y, and Z components for the + * corner of the axis-aligned bounding box that contains this region. + * + * @return the maximum point + */ + public BlockVector3 getMaximumPoint() { + return max; + } + + /** + * Get the priority of the region, where higher numbers indicate a higher + * priority. + * + * @return the priority + */ + public int getPriority() { + return priority; + } + + /** + * Set the priority of the region, where higher numbers indicate a higher + * priority. + * + * @param priority the priority to set + */ + public void setPriority(int priority) { + setDirty(true); + this.priority = priority; + } + + /** + * Get the parent of the region, if one exists. + * + * @return the parent, or {@code null} + */ + @Nullable + public ProtectedRegion getParent() { + return parent; + } + + /** + * Set the parent of this region. This checks to make sure that it will + * not result in circular inheritance. + * + * @param parent the new parent + * @throws CircularInheritanceException when circular inheritance is detected + */ + public void setParent(@Nullable ProtectedRegion parent) throws CircularInheritanceException { + setDirty(true); + + if (parent == null) { + this.parent = null; + return; + } + + if (parent == this) { + throw new CircularInheritanceException(); + } + + ProtectedRegion p = parent.getParent(); + while (p != null) { + if (p == this) { + throw new CircularInheritanceException(); + } + p = p.getParent(); + } + + this.parent = parent; + } + + /** + * Clear the parent (set the parent to {@code null}). + */ + public void clearParent() { + setDirty(true); + this.parent = null; + } + + /** + * Get the domain that contains the owners of this region. + * + * @return the domain + */ + public DefaultDomain getOwners() { + return owners; + } + + /** + * Set the owner domain. + * + * @param owners the new domain + */ + public void setOwners(DefaultDomain owners) { + checkNotNull(owners); + setDirty(true); + this.owners = new DefaultDomain(owners); + } + + /** + * Get the domain that contains the members of this region, which does + * not automatically include the owners. + * + * @return the members + */ + public DefaultDomain getMembers() { + return members; + } + + /** + * Set the members domain. + * + * @param members the new domain + */ + public void setMembers(DefaultDomain members) { + checkNotNull(members); + setDirty(true); + this.members = new DefaultDomain(members); + } + + /** + * Checks whether a region has members or owners. + * + * @return whether there are members or owners + */ + public boolean hasMembersOrOwners() { + return owners.size() > 0 || members.size() > 0; + } + + /** + * Checks whether a player is an owner of region or any of its parents. + * + * @param player player to check + * @return whether an owner + */ + public boolean isOwner(LocalPlayer player) { + checkNotNull(player); + + if (owners.contains(player)) { + return true; + } + + ProtectedRegion curParent = getParent(); + while (curParent != null) { + if (curParent.getOwners().contains(player)) { + return true; + } + + curParent = curParent.getParent(); + } + + return false; + } + + /** + * Checks whether a player is an owner of region or any of its parents. + * + * @param playerName player name to check + * @return whether an owner + * @deprecated Names are deprecated, this will not return owners added by UUID (LocalPlayer) + */ + @Deprecated + public boolean isOwner(String playerName) { + checkNotNull(playerName); + + if (owners.contains(playerName)) { + return true; + } + + ProtectedRegion curParent = getParent(); + while (curParent != null) { + if (curParent.getOwners().contains(playerName)) { + return true; + } + + curParent = curParent.getParent(); + } + + return false; + } + + /** + * Checks whether a player is a member OR OWNER of the region + * or any of its parents. + * + * @param player player to check + * @return whether an owner or member + */ + public boolean isMember(LocalPlayer player) { + checkNotNull(player); + + if (isOwner(player)) { + return true; + } + + return isMemberOnly(player); + } + + /** + * Checks whether a player is a member OR OWNER of the region + * or any of its parents. + * + * @param playerName player name to check + * @return whether an owner or member + * @deprecated Names are deprecated, this will not return players added by UUID (LocalPlayer) + */ + @Deprecated + public boolean isMember(String playerName) { + checkNotNull(playerName); + + if (isOwner(playerName)) { + return true; + } + + if (members.contains(playerName)) { + return true; + } + + ProtectedRegion curParent = getParent(); + while (curParent != null) { + if (curParent.getMembers().contains(playerName)) { + return true; + } + + curParent = curParent.getParent(); + } + + return false; + } + + /** + * Checks whether a player is a member of the region or any of its parents. + * + * @param player player to check + * @return whether an member + */ + public boolean isMemberOnly(LocalPlayer player) { + checkNotNull(player); + + if (members.contains(player)) { + return true; + } + + ProtectedRegion curParent = getParent(); + while (curParent != null) { + if (curParent.getMembers().contains(player)) { + return true; + } + + curParent = curParent.getParent(); + } + + return false; + } + + /** + * Get a flag's value. + * + * @param flag the flag to check + * @return the value or null if isn't defined + * @param the flag type + * @param the type of the flag's value + */ + @SuppressWarnings("unchecked") + @Nullable + public , V> V getFlag(T flag) { + checkNotNull(flag); + + Object obj = flags.get(flag); + V val; + + if (obj != null) { + val = (V) obj; + } else { + return null; + } + + return val; + } + + /** + * Set a flag's value. + * + * @param flag the flag to check + * @param val the value to set + * @param the flag type + * @param the type of the flag's value + */ + public , V> void setFlag(T flag, @Nullable V val) { + checkNotNull(flag); + setDirty(true); + + if (val == null) { + flags.remove(flag); + } else { + flags.put(flag, val); + } + } + + /** + * Get the map of flags. + * + * @return the map of flags currently used for this region + */ + public Map, Object> getFlags() { + return flags; + } + + /** + * Set the map of flags. + * + *

A copy of the map will be used.

+ * + * @param flags the flags to set + */ + public void setFlags(Map, Object> flags) { + checkNotNull(flags); + + setDirty(true); + this.flags = new ConcurrentHashMap<>(flags); + } + + /** + * Copy attributes from another region. + * + * @param other the other region + */ + public void copyFrom(ProtectedRegion other) { + checkNotNull(other); + setMembers(other.getMembers()); + setOwners(other.getOwners()); + setFlags(other.getFlags()); + setPriority(other.getPriority()); + try { + setParent(other.getParent()); + } catch (CircularInheritanceException ignore) { + // This should not be thrown + } + } + + /** + * Get points of the region projected onto the X-Z plane. + * + * @return the points + */ + public abstract List getPoints(); + + /** + * Get the number of blocks in this region. + * + * @return the volume of this region in blocks + */ + public abstract int volume(); + + /** + * Check to see if a point is inside this region. + * + * @param pt The point to check + * @return Whether {@code pt} is in this region + */ + public abstract boolean contains(BlockVector3 pt); + + /** + * Check to see if a position is contained within this region. + * + * @param position the position to check + * @return whether {@code position} is in this region + */ + public boolean contains(BlockVector2 position) { + checkNotNull(position); + return contains(BlockVector3.at(position.getBlockX(), min.getBlockY(), position.getBlockZ())); + } + + /** + * Check to see if a point is inside this region. + * + * @param x the x coordinate to check + * @param y the y coordinate to check + * @param z the z coordinate to check + * @return whether this region contains the point + */ + public boolean contains(int x, int y, int z) { + return contains(BlockVector3.at(x, y, z)); + } + + /** + * Check to see if any of the points are inside this region projected + * onto the X-Z plane. + * + * @param positions a list of positions + * @return true if contained + */ + public boolean containsAny(List positions) { + checkNotNull(positions); + + for (BlockVector2 pt : positions) { + if (contains(pt)) { + return true; + } + } + + return false; + } + + /** + * Get the type of region. + * + * @return the type + */ + public abstract RegionType getType(); + + /** + * Return a list of regions from the given list of regions that intersect + * with this region. + * + * @param regions a list of regions to source from + * @return the elements of {@code regions} that intersect with this region + */ + public List getIntersectingRegions(Collection regions) { + checkNotNull(regions, "regions"); + + List intersecting = Lists.newArrayList(); + Area thisArea = toArea(); + + for (ProtectedRegion region : regions) { + if (!region.isPhysicalArea()) continue; + + if (intersects(region, thisArea)) { + intersecting.add(region); + } + } + + return intersecting; + } + + /** + * Test whether the given region intersects with this area. + * + * @param region the region to test + * @param thisArea an area object for this region + * @return true if the two regions intersect + */ + protected boolean intersects(ProtectedRegion region, Area thisArea) { + if (intersectsBoundingBox(region)) { + Area testArea = region.toArea(); + testArea.intersect(thisArea); + return !testArea.isEmpty(); + } else { + return false; + } + } + + /** + * Checks if the bounding box of a region intersects with with the bounding + * box of this region. + * + * @param region the region to check + * @return whether the given region intersects + */ + protected boolean intersectsBoundingBox(ProtectedRegion region) { + BlockVector3 rMaxPoint = region.getMaximumPoint(); + BlockVector3 min = getMinimumPoint(); + + if (rMaxPoint.getBlockX() < min.getBlockX()) return false; + if (rMaxPoint.getBlockY() < min.getBlockY()) return false; + if (rMaxPoint.getBlockZ() < min.getBlockZ()) return false; + + BlockVector3 rMinPoint = region.getMinimumPoint(); + BlockVector3 max = getMaximumPoint(); + + if (rMinPoint.getBlockX() > max.getBlockX()) return false; + if (rMinPoint.getBlockY() > max.getBlockY()) return false; + if (rMinPoint.getBlockZ() > max.getBlockZ()) return false; + + return true; + } + + /** + * Compares all edges of two regions to see if any of them intersect. + * + * @param region the region to check + * @return whether any edges of a region intersect + */ + protected boolean intersectsEdges(ProtectedRegion region) { + List pts1 = getPoints(); + List pts2 = region.getPoints(); + BlockVector2 lastPt1 = pts1.get(pts1.size() - 1); + BlockVector2 lastPt2 = pts2.get(pts2.size() - 1); + for (BlockVector2 aPts1 : pts1) { + for (BlockVector2 aPts2 : pts2) { + + Line2D line1 = new Line2D.Double( + lastPt1.getBlockX(), + lastPt1.getBlockZ(), + aPts1.getBlockX(), + aPts1.getBlockZ()); + + if (line1.intersectsLine( + lastPt2.getBlockX(), + lastPt2.getBlockZ(), + aPts2.getBlockX(), + aPts2.getBlockZ())) { + return true; + } + lastPt2 = aPts2; + } + lastPt1 = aPts1; + } + return false; + } + + /** + * Return the AWT area, otherwise null if + * {@link #isPhysicalArea()} if false. + * + * @return The shape version + */ + abstract Area toArea(); + + /** + * @return true if this region should only be kept in memory and not be saved + */ + public boolean isTransient() { + return transientRegion; + } + + /** + * @return true if this region is not transient and changes have been made. + */ + @Override + public boolean isDirty() { + if (isTransient()) { + return false; + } + return dirty || owners.isDirty() || members.isDirty(); + } + + @Override + public void setDirty(boolean dirty) { + this.dirty = dirty; + owners.setDirty(dirty); + members.setDirty(dirty); + } + + @Override + public int compareTo(ProtectedRegion other) { + if (getPriority() > other.getPriority()) { + return -1; + } else if (getPriority() < other.getPriority()) { + return 1; + } + + return getId().compareTo(other.getId()); + } + + @Override + public int hashCode(){ + return id.hashCode(); + } + + // ** This equals doesn't take the region manager into account (since it's not available anyway) + // ** and thus will return equality for two regions with the same name in different worlds (or even + // ** no world at all, such as when testing intersection with a dummy region). Thus, we keep the + // ** hashCode method to improve hashset operation (which is fine within one manager - ids are unique) + // ** and will only leave a collision when regions in different managers with the same id are tested. + // ** In that case, they are checked for reference equality. Note that is it possible to programmatically + // ** add the same region object to two different managers. If someone uses the API in this way, it is expected + // ** that the regions be the same reference in both worlds. (Though that might lead to other odd behavior) + /* + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ProtectedRegion)) { + return false; + } + + ProtectedRegion other = (ProtectedRegion) obj; + return other.getId().equals(getId()); + } + */ + + @Override + public String toString() { + return "ProtectedRegion{" + + "id='" + id + "', " + + "type='" + getType() + '\'' + + '}'; + } + + /** + * Checks to see if the given ID is a valid ID. + * + * @param id the id to check + * @return whether the region id given is valid + */ + public static boolean isValidId(String id) { + checkNotNull(id); + return VALID_ID_PATTERN.matcher(id).matches(); + } + + /** + * Thrown when setting a parent would create a circular inheritance + * situation. + */ + public static class CircularInheritanceException extends Exception { + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegionMBRConverter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegionMBRConverter.java new file mode 100644 index 000000000..006c0431c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/ProtectedRegionMBRConverter.java @@ -0,0 +1,56 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import org.khelekore.prtree.MBRConverter; + +public class ProtectedRegionMBRConverter implements MBRConverter { + + @Override + public int getDimensions() { + return 3; + } + + @Override + public double getMax(int dimension, ProtectedRegion region) { + switch (dimension) { + case 0: + return region.getMaximumPoint().getBlockX(); + case 1: + return region.getMaximumPoint().getBlockY(); + case 2: + return region.getMaximumPoint().getBlockZ(); + } + return 0; + } + + @Override + public double getMin(int dimension, ProtectedRegion region) { + switch (dimension) { + case 0: + return region.getMinimumPoint().getBlockX(); + case 1: + return region.getMinimumPoint().getBlockY(); + case 2: + return region.getMinimumPoint().getBlockZ(); + } + return 0; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/QueryCache.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/QueryCache.java new file mode 100644 index 000000000..0dc290786 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/QueryCache.java @@ -0,0 +1,115 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.RegionResultSet; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.RegionQuery.QueryOption; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Keeps a cache of {@link RegionResultSet}s. The contents of the cache + * must be externally invalidated occasionally (and frequently). + * + *

This class is fully concurrent.

+ */ +public class QueryCache { + + private final ConcurrentMap> cache = new ConcurrentHashMap<>(16, 0.75f, 2); + + /** + * Get from the cache a {@code ApplicableRegionSet} if an entry exists; + * otherwise, query the given manager for a result and cache it. + * + * @param manager the region manager + * @param location the location + * @param option the option + * @return a result + */ + public ApplicableRegionSet queryContains(RegionManager manager, Location location, QueryOption option) { + checkNotNull(manager); + checkNotNull(location); + checkNotNull(option); + + CacheKey key = new CacheKey(location); + return cache.compute(key, (k, v) -> option.createCache(manager, location, v)).get(option); + } + + /** + * Invalidate the cache and clear its contents. + */ + public void invalidateAll() { + cache.clear(); + } + + /** + * Key object for the map. + */ + private static class CacheKey { + private final World world; + private final int x; + private final int y; + private final int z; + private final int hashCode; + + private CacheKey(Location location) { + this.world = (World) location.getExtent(); + this.x = location.getBlockX(); + this.y = location.getBlockY(); + this.z = location.getBlockZ(); + + // Pre-compute hash code + int result = world.hashCode(); + result = 31 * result + x; + result = 31 * result + y; + result = 31 * result + z; + this.hashCode = result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CacheKey cacheKey = (CacheKey) o; + + if (x != cacheKey.x) return false; + if (y != cacheKey.y) return false; + if (z != cacheKey.z) return false; + if (!world.equals(cacheKey.world)) return false; + + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionContainer.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionContainer.java new file mode 100644 index 000000000..42db18505 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionContainer.java @@ -0,0 +1,223 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.protection.managers.RegionContainerImpl; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.migration.Migration; +import com.sk89q.worldguard.protection.managers.migration.MigrationException; +import com.sk89q.worldguard.protection.managers.migration.UUIDMigration; +import com.sk89q.worldguard.protection.managers.storage.RegionDriver; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; + +import javax.annotation.Nullable; + +/** + * A region container creates {@link RegionManager}s for loaded worlds, which + * allows access to the region data of a world. Generally, only data is + * loaded for worlds that are loaded in the server. + * + *

This class is thread safe and its contents can be accessed from + * multiple concurrent threads.

+ */ +public abstract class RegionContainer { + + protected final Object lock = new Object(); + protected final QueryCache cache = new QueryCache(); + protected RegionContainerImpl container; + + /** + * Initialize the region container. + */ + public void initialize() { + ConfigurationManager config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + container = new RegionContainerImpl(config.selectedRegionStoreDriver, WorldGuard.getInstance().getFlagRegistry()); + + loadWorlds(); + + // Migrate to UUIDs + autoMigrate(); + } + + /** + * Save data and unload. + */ + public void unload() { + synchronized (lock) { + container.unloadAll(); + } + } + + /** + * Get the region store driver. + * + * @return the driver + */ + public RegionDriver getDriver() { + return container.getDriver(); + } + + /** + * Reload the region container. + * + *

This method may block until the data for all loaded worlds has been + * unloaded and new data has been loaded.

+ */ + public void reload() { + synchronized (lock) { + unload(); + loadWorlds(); + } + } + + /** + * Get the region manager for a world if one exists. + * + *

If you wish to make queries and performance is more important + * than accuracy, use {@link #createQuery()} instead.

+ * + *

This method may return {@code null} if region data for the given + * world has not been loaded, has failed to load, or support for regions + * has been disabled.

+ * + * @param world the world + * @return a region manager, or {@code null} if one is not available + */ + @Nullable + public RegionManager get(World world) { + return container.get(world.getName()); + } + + /** + * Get an immutable list of loaded {@link RegionManager}s. + * + * @return a list of managers + */ + public List getLoaded() { + return Collections.unmodifiableList(container.getLoaded()); + } + + /** + * Get the a set of region managers that are failing to save. + * + * @return a set of region managers + */ + public Set getSaveFailures() { + return container.getSaveFailures(); + } + + /** + * Create a new region query. + * + * @return a new query + */ + public RegionQuery createQuery() { + return new RegionQuery(cache); + } + + /** + * Execute a migration and block any loading of region data during + * the migration. + * + * @param migration the migration + * @throws MigrationException thrown by the migration on error + */ + public void migrate(Migration migration) throws MigrationException { + checkNotNull(migration); + + synchronized (lock) { + try { + WorldGuard.logger.info("Unloading and saving region data that is currently loaded..."); + unload(); + migration.migrate(); + } finally { + WorldGuard.logger.info("Loading region data for loaded worlds..."); + loadWorlds(); + } + } + } + + /** + * Try loading the region managers for all currently loaded worlds. + */ + protected void loadWorlds() { + WorldGuard.logger.info("Loading region data..."); + synchronized (lock) { + for (World world : WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds()) { + load(world); + } + } + } + + /** + * Unload the region data for a world. + * + * @param world a world + */ + public void unload(World world) { + checkNotNull(world); + + synchronized (lock) { + container.unload(world.getName()); + } + } + + /** + * Execute auto-migration. + */ + protected void autoMigrate() { + ConfigurationManager config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + + if (config.migrateRegionsToUuid) { + RegionDriver driver = getDriver(); + UUIDMigration migrator = new UUIDMigration(driver, WorldGuard.getInstance().getProfileService(), WorldGuard.getInstance().getFlagRegistry()); + migrator.setKeepUnresolvedNames(config.keepUnresolvedNames); + try { + migrate(migrator); + + WorldGuard.logger.info("Regions saved after UUID migration! This won't happen again unless " + + "you change the relevant configuration option in WorldGuard's config."); + + config.disableUuidMigration(); + } catch (MigrationException e) { + WorldGuard.logger.log(Level.WARNING, "Failed to execute the migration", e); + } + } + } + + /** + * Load the region data for a world if it has not been loaded already. + * + * @param world the world + * @return a region manager, either returned from the cache or newly loaded + */ + @Nullable protected abstract RegionManager load(World world); +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionQuery.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionQuery.java new file mode 100644 index 000000000..2ee030c1e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionQuery.java @@ -0,0 +1,643 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.FailedLoadRegionSet; +import com.sk89q.worldguard.protection.PermissiveRegionSet; +import com.sk89q.worldguard.protection.RegionResultSet; +import com.sk89q.worldguard.protection.association.RegionAssociable; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.index.RegionIndex; +import com.sk89q.worldguard.protection.util.NormativeOrders; +import com.sk89q.worldguard.protection.util.RegionCollectionConsumer; + +import java.util.Collection; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.Nullable; + +/** + * This object allows easy spatial queries involving region data for the + * purpose of implementing protection / region flag checks. + * + *

Results may be cached for brief amounts of time. If you want to get + * data for the purposes of changing it, use of this class is not recommended. + * Some of the return values of the methods may be simulated to reduce + * boilerplate code related to implementing protection, meaning that false + * data is returned.

+ */ +public class RegionQuery { + + private final ConfigurationManager config; + private final QueryCache cache; + + /** + * Create a new instance. + * + * @param cache the query cache + */ + public RegionQuery(QueryCache cache) { + checkNotNull(cache); + + this.config = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + this.cache = cache; + } + + /** + * Query for regions containing the given location. + * + *

{@link QueryOption#COMPUTE_PARENTS} is used.

+ * + *

An instance of {@link ApplicableRegionSet} will always be returned, + * even if regions are disabled or region data failed to load. An + * appropriate "virtual" set will be returned in such a case (for example, + * if regions are disabled, the returned set would permit all + * activities).

+ * + * @param location the location + * @return a region set + */ + public ApplicableRegionSet getApplicableRegions(Location location) { + return getApplicableRegions(location, QueryOption.COMPUTE_PARENTS); + } + + /** + * Query for regions containing the given location. + * + *

An instance of {@link ApplicableRegionSet} will always be returned, + * even if regions are disabled or region data failed to load. An + * appropriate "virtual" set will be returned in such a case (for example, + * if regions are disabled, the returned set would permit all + * activities).

+ * + * @param location the location + * @param option the option + * @return a region set + */ + public ApplicableRegionSet getApplicableRegions(Location location, QueryOption option) { + checkNotNull(location); + checkNotNull(option); + + World world = (World) location.getExtent(); + WorldConfiguration worldConfig = config.get(world); + + if (!worldConfig.useRegions) { + return PermissiveRegionSet.getInstance(); + } + + RegionManager manager = WorldGuard.getInstance().getPlatform().getRegionContainer().get((World) location.getExtent()); + if (manager != null) { + return cache.queryContains(manager, location, option); + } else { + return FailedLoadRegionSet.getInstance(); + } + } + + /** + * Returns true if the BUILD flag allows the action in the location, but it + * can be overridden by a list of other flags. The BUILD flag will not + * override the other flags, but the other flags can override BUILD. If + * neither BUILD or any of the flags permit the action, then false will + * be returned. + * + *

Use this method when checking flags that are related to build + * protection. For example, lighting fire in a region should not be + * permitted unless the player is a member of the region or the + * LIGHTER flag allows it. However, the LIGHTER flag should be able + * to allow lighting fires even if BUILD is set to DENY.

+ * + *

How this method works (BUILD can be overridden by other flags but + * not the other way around) is inconsistent, but it's required for + * legacy reasons.

+ * + *

This method does not check the region bypass permission. That must + * be done by the calling code.

+ * + * @param location the location + * @param player an optional player, which would be used to determine the region group to apply + * @param flag the flag + * @return true if the result was {@code ALLOW} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + public boolean testBuild(Location location, LocalPlayer player, StateFlag... flag) { + if (flag.length == 0) { + return testState(location, player, Flags.BUILD); + } + + return StateFlag.test(StateFlag.combine( + StateFlag.denyToNone(queryState(location, player, Flags.BUILD)), + queryState(location, player, flag))); + } + + /** + * Returns true if the BUILD flag allows the action in the location, but it + * can be overridden by a list of other flags. The BUILD flag will not + * override the other flags, but the other flags can override BUILD. If + * neither BUILD or any of the flags permit the action, then false will + * be returned. + * + *

Use this method when checking flags that are related to build + * protection. For example, lighting fire in a region should not be + * permitted unless the player is a member of the region or the + * LIGHTER flag allows it. However, the LIGHTER flag should be able + * to allow lighting fires even if BUILD is set to DENY.

+ * + *

How this method works (BUILD can be overridden by other flags but + * not the other way around) is inconsistent, but it's required for + * legacy reasons.

+ * + *

This method does not check the region bypass permission. That must + * be done by the calling code.

+ * + * @param location the location + * @param associable an optional associable + * @param flag the flag + * @return true if the result was {@code ALLOW} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + public boolean testBuild(Location location, RegionAssociable associable, StateFlag... flag) { + if (flag.length == 0) { + return testState(location, associable, Flags.BUILD); + } + + return StateFlag.test(StateFlag.combine( + StateFlag.denyToNone(queryState(location, associable, Flags.BUILD)), + queryState(location, associable, flag))); + } + + /** + * Returns true if the BUILD flag allows the action in the location, but it + * can be overridden by a list of other flags. The BUILD flag will not + * override the other flags, but the other flags can override BUILD. If + * neither BUILD or any of the flags permit the action, then false will + * be returned. + * + *

Use this method when checking flags that are related to build + * protection. For example, lighting fire in a region should not be + * permitted unless the player is a member of the region or the + * LIGHTER flag allows it. However, the LIGHTER flag should be able + * to allow lighting fires even if BUILD is set to DENY.

+ * + *

This method does include parameters for a {@link MapFlag}.

+ * + *

How this method works (BUILD can be overridden by other flags but + * not the other way around) is inconsistent, but it's required for + * legacy reasons.

+ * + *

This method does not check the region bypass permission. That must + * be done by the calling code.

+ * + * @param location the location + * @param associable an optional associable + * @param mapFlag the MapFlag + * @param key the key for the MapFlag + * @param fallback the fallback flag for MapFlag + * @param flag the flags + * @return true if the result was {@code ALLOW} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + public boolean testBuild(Location location, RegionAssociable associable, MapFlag mapFlag, K key, + @Nullable StateFlag fallback, StateFlag... flag) { + if (mapFlag == null) + return testBuild(location, associable, flag); + + if (flag.length == 0) { + return StateFlag.test(StateFlag.combine( + StateFlag.denyToNone(queryState(location, associable, Flags.BUILD)), + queryMapValue(location, associable, mapFlag, key, fallback) + )); + } + + return StateFlag.test(StateFlag.combine( + StateFlag.denyToNone(queryState(location, associable, Flags.BUILD)), + queryMapValue(location, associable, mapFlag, key, fallback), + queryState(location, associable, flag) + )); + } + + /** + * Test whether the (effective) value for a list of state flags equals + * {@code ALLOW}. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + *

This method does not check the region bypass permission. That must + * be done by the calling code.

+ * + * @param location the location + * @param player an optional player, which would be used to determine the region group to apply + * @param flag the flag + * @return true if the result was {@code ALLOW} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + public boolean testState(Location location, @Nullable LocalPlayer player, StateFlag... flag) { + return StateFlag.test(queryState(location, player, flag)); + } + + /** + * Test whether the (effective) value for a list of state flags equals + * {@code ALLOW}. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + *

This method does not check the region bypass permission. That must + * be done by the calling code.

+ * + * @param location the location + * @param associable an optional associable + * @param flag the flag + * @return true if the result was {@code ALLOW} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + public boolean testState(Location location, @Nullable RegionAssociable associable, StateFlag... flag) { + return StateFlag.test(queryState(location, associable, flag)); + } + + /** + * Get the (effective) value for a list of state flags. The rules of + * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, + * and {@code ALLOW} overrides {@code NONE}. One flag may override another. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + * @param location the location + * @param player an optional player, which would be used to determine the region groups that apply + * @param flags a list of flags to check + * @return a state + * @see RegionResultSet#queryState(RegionAssociable, StateFlag...) + */ + @Nullable + public State queryState(Location location, @Nullable LocalPlayer player, StateFlag... flags) { + return getApplicableRegions(location).queryState(player, flags); + } + + /** + * Get the (effective) value for a list of state flags. The rules of + * states is observed here; that is, {@code DENY} overrides {@code ALLOW}, + * and {@code ALLOW} overrides {@code NONE}. One flag may override another. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is in the list of flags.

+ * + * @param location the location + * @param associable an optional associable + * @param flags a list of flags to check + * @return a state + * @see RegionResultSet#queryState(RegionAssociable, StateFlag...) + */ + @Nullable + public State queryState(Location location, @Nullable RegionAssociable associable, StateFlag... flags) { + return getApplicableRegions(location).queryState(associable, flags); + } + + /** + * Get the effective value for a flag. If there are multiple values + * (for example, multiple overlapping regions with + * the same priority may have the same flag set), then the selected + * (or "winning") value will depend on the flag type. + * + *

Only some flag types actually have a strategy for picking the + * "best value." For most types, the actual value that is chosen to be + * returned is undefined (it could be any value). As of writing, the only + * type of flag that actually has a strategy for picking a value is the + * {@link StateFlag}.

+ * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param location the location + * @param player an optional player, which would be used to determine the region group to apply + * @param flag the flag + * @return a value, which could be {@code null} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + @Nullable + public V queryValue(Location location, @Nullable LocalPlayer player, Flag flag) { + return getApplicableRegions(location).queryValue(player, flag); + } + + /** + * Get the effective value for a flag. If there are multiple values + * (for example, multiple overlapping regions with + * the same priority may have the same flag set), then the selected + * (or "winning") value will depend on the flag type. + * + *

Only some flag types actually have a strategy for picking the + * "best value." For most types, the actual value that is chosen to be + * returned is undefined (it could be any value). As of writing, the only + * type of flag that actually has a strategy for picking a value is the + * {@link StateFlag}.

+ * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param location the location + * @param associable an optional associable + * @param flag the flag + * @return a value, which could be {@code null} + * @see RegionResultSet#queryValue(RegionAssociable, Flag) + */ + @Nullable + public V queryValue(Location location, @Nullable RegionAssociable associable, Flag flag) { + return getApplicableRegions(location).queryValue(associable, flag); + } + + /** + * Get the effective value for a key in a {@link MapFlag}. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will be undefined. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag of type {@link MapFlag} + * @param key the key for the map flag + * @return a value, which could be {@code null} + */ + @Nullable + public V queryMapValue(Location location, @Nullable RegionAssociable subject, MapFlag flag, K key) { + return getApplicableRegions(location).queryMapValue(subject, flag, key); + } + + /** + * Get the effective value for a key in a {@link MapFlag}. If there are multiple values + * (for example, if there are multiple regions with the same priority + * but with different farewell messages set, there would be multiple + * completing values), then the selected (or "winning") value will be undefined. + * + *

A subject can be provided that is used to determine whether the value + * of a flag on a particular region should be used. For example, if a + * flag's region group is set to {@link RegionGroup#MEMBERS} and the given + * subject is not a member, then the region would be skipped when + * querying that flag. If {@code null} is provided for the subject, then + * only flags that use {@link RegionGroup#ALL}, + * {@link RegionGroup#NON_MEMBERS}, etc. will apply.

+ * + *

It's possible to provide a fallback flag for the case when the key doesn't + * exist in the {@link MapFlag}.

+ * + * @param subject an optional subject, which would be used to determine the region group to apply + * @param flag the flag of type {@link MapFlag} + * @param key the key for the map flag + * @param fallback the fallback flag + * @return a value, which could be {@code null} + */ + @Nullable + public V queryMapValue(Location location, @Nullable RegionAssociable subject, MapFlag flag, K key, Flag fallback) { + return getApplicableRegions(location).queryMapValue(subject, flag, key, fallback); + } + + /** + * Get the effective values for a flag, returning a collection of all + * values. It is up to the caller to determine which value, if any, + * from the collection will be used. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param location the location + * @param player an optional player, which would be used to determine the region group to apply + * @param flag the flag + * @return a collection of values + * @see RegionResultSet#queryAllValues(RegionAssociable, Flag) + */ + public Collection queryAllValues(Location location, @Nullable LocalPlayer player, Flag flag) { + return getApplicableRegions(location).queryAllValues(player, flag); + } + + /** + * Get the effective values for a flag, returning a collection of all + * values. It is up to the caller to determine which value, if any, + * from the collection will be used. + * + *

{@code player} can be non-null to satisfy region group requirements, + * otherwise it will be assumed that the caller that is not a member of any + * regions. (Flags on a region can be changed so that they only apply + * to certain users.) The player argument is required if the + * {@link Flags#BUILD} flag is the flag being queried.

+ * + * @param location the location + * @param associable an optional associable + * @param flag the flag + * @return a collection of values + * @see RegionResultSet#queryAllValues(RegionAssociable, Flag) + */ + public Collection queryAllValues(Location location, @Nullable RegionAssociable associable, Flag flag) { + return getApplicableRegions(location).queryAllValues(associable, flag); + } + + /** + * Options for constructing a region set via + * {@link #getApplicableRegions(Location, QueryOption)} for example. + */ + public enum QueryOption { + /** + * Constructs a region set that does not include parent regions and + * may be left unsorted (but a cached, sorted set of the same regions + * may be returned). + */ + NONE(false) { + @Override + public List constructResult(Set applicable) { + return ImmutableList.copyOf(applicable); + } + + @Override + Map createCache(RegionManager manager, Location location, Map cache) { + if (cache == null) { + cache = new EnumMap<>(QueryOption.class); + cache.put(QueryOption.NONE, manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.NONE)); + } + + // If c != null, we can assume that Option.NONE is present. + return cache; + } + }, + + /** + * Constructs a region set that does not include parent regions and is + * sorted by {@link NormativeOrders}. + */ + SORT(false) { + @Override + Map createCache(RegionManager manager, Location location, Map cache) { + if (cache == null) { + Map newCache = new EnumMap<>(QueryOption.class); + ApplicableRegionSet result = manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.SORT); + newCache.put(QueryOption.NONE, result); + newCache.put(QueryOption.SORT, result); + return newCache; + } else { + // If c != null, we can assume that Option.NONE is present. + cache.computeIfAbsent(QueryOption.SORT, k -> new RegionResultSet(cache.get(QueryOption.NONE).getRegions(), manager.getRegion("__global__"))); + return cache; + } + } + }, + + /** + * Constructs a region set that includes parent regions and is sorted by + * {@link NormativeOrders}. + */ + COMPUTE_PARENTS(true) { + @Override + Map createCache(RegionManager manager, Location location, Map cache) { + if (cache == null) { + Map newCache = new EnumMap<>(QueryOption.class); + ApplicableRegionSet noParResult = manager.getApplicableRegions(location.toVector().toBlockPoint(), QueryOption.NONE); + Set noParRegions = noParResult.getRegions(); + Set regions = new HashSet<>(); + noParRegions.forEach(new RegionCollectionConsumer(regions, true)::apply); + ApplicableRegionSet result = new RegionResultSet(regions, manager.getRegion("__global__")); + + if (regions.size() == noParRegions.size()) { + newCache.put(QueryOption.NONE, result); + newCache.put(QueryOption.SORT, result); + } else { + newCache.put(QueryOption.NONE, noParResult); + } + + newCache.put(QueryOption.COMPUTE_PARENTS, result); + return newCache; + } + + cache.computeIfAbsent(QueryOption.COMPUTE_PARENTS, k -> { + Set regions = new HashSet<>(); + ApplicableRegionSet result = cache.get(QueryOption.SORT); + boolean sorted = true; + + if (result == null) { + // If c != null, we can assume that Option.NONE is present. + result = cache.get(QueryOption.NONE); + sorted = false; + } + + Set noParRegions = result.getRegions(); + noParRegions.forEach(new RegionCollectionConsumer(regions, true)::apply); + + if (sorted && regions.size() == noParRegions.size()) { + return result; + } + + result = new RegionResultSet(regions, manager.getRegion("__global__")); + + if (regions.size() == noParRegions.size()) { + cache.put(QueryOption.SORT, result); + } + + return result; + }); + return cache; + } + }; + + private final boolean collectParents; + + QueryOption(boolean collectParents) { + this.collectParents = collectParents; + } + + /** + * Create a {@link RegionCollectionConsumer} with the given collection + * used for the {@link RegionIndex}. Internal API. + * + * @param collection the collection + * @return a region collection consumer + */ + public RegionCollectionConsumer createIndexConsumer(Collection collection) { + return new RegionCollectionConsumer(collection, collectParents); + } + + /** + * Convert the set of regions to a list. Sort and add parents if + * necessary. Internal API. + * + * @param applicable the set of regions + * @return a list of regions + */ + public List constructResult(Set applicable) { + return NormativeOrders.fromSet(applicable); + } + + /** + * Create (if null) or update the given cache map with at least an entry + * for this option if necessary and return it. + * + * @param manager the manager + * @param location the location + * @param cache the cache map + * @return a cache map + */ + abstract Map createCache(RegionManager manager, Location location, Map cache); + + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java new file mode 100644 index 000000000..5e57e7c3e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/regions/RegionType.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + +/** + * An enum of supported region types. + */ +public enum RegionType { + + // Do not change the names + CUBOID("cuboid"), + POLYGON("poly2d"), + GLOBAL("global"); + + private final String name; + + /** + * Create a new instance. + * + * @param name the region name + */ + RegionType(String name) { + this.name = name; + } + + /** + * Get the name of the region. + * + * @return the name of the region + */ + public String getName() { + return name; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java new file mode 100644 index 000000000..4c8a00ff8 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/DomainInputResolver.java @@ -0,0 +1,180 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.util; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.sk89q.worldguard.util.profile.Profile; +import com.sk89q.worldguard.util.profile.resolver.ProfileService; +import com.sk89q.worldguard.util.profile.util.UUIDs; +import com.sk89q.worldguard.domains.DefaultDomain; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Resolves input for a domain (i.e. "player1 player2 <uuid> g:group"). + */ +public class DomainInputResolver implements Callable { + + private static final Pattern GROUP_PATTERN = Pattern.compile("(?i)^[G]:(.+)$"); + + /** + * The policy for locating users. + */ + public enum UserLocatorPolicy { + UUID_ONLY, + NAME_ONLY, + UUID_AND_NAME + } + + private final ProfileService profileService; + private final String[] input; + private UserLocatorPolicy locatorPolicy = UserLocatorPolicy.UUID_ONLY; + + /** + * Create a new instance. + * + * @param profileService the profile service + * @param input the input to parse + */ + public DomainInputResolver(ProfileService profileService, String[] input) { + checkNotNull(profileService); + checkNotNull(input); + this.profileService = profileService; + this.input = input; + } + + /** + * Get the policy used for identifying users. + * + * @return the policy + */ + public UserLocatorPolicy getLocatorPolicy() { + return locatorPolicy; + } + + /** + * Set the policy used for identifying users. + * + * @param locatorPolicy the policy + */ + public void setLocatorPolicy(UserLocatorPolicy locatorPolicy) { + checkNotNull(locatorPolicy); + this.locatorPolicy = locatorPolicy; + } + + @Override + public DefaultDomain call() throws UnresolvedNamesException { + DefaultDomain domain = new DefaultDomain(); + List namesToQuery = new ArrayList<>(); + + for (String s : input) { + Matcher m = GROUP_PATTERN.matcher(s); + if (m.matches()) { + domain.addGroup(m.group(1)); + } else { + UUID uuid = parseUUID(s); + if (uuid != null) { + // Try to add any UUIDs given + domain.addPlayer(UUID.fromString(UUIDs.addDashes(s.replaceAll("^uuid:", "")))); + } else { + switch (locatorPolicy) { + case NAME_ONLY: + domain.addPlayer(s); + break; + case UUID_ONLY: + namesToQuery.add(s.toLowerCase()); + break; + case UUID_AND_NAME: + domain.addPlayer(s); + namesToQuery.add(s.toLowerCase()); + } + } + } + } + + if (!namesToQuery.isEmpty()) { + try { + for (Profile profile : profileService.findAllByName(namesToQuery)) { + namesToQuery.remove(profile.getName().toLowerCase()); + domain.addPlayer(profile.getUniqueId()); + } + } catch (IOException e) { + throw new UnresolvedNamesException("The UUID lookup service failed so the names entered could not be turned into UUIDs"); + } catch (InterruptedException e) { + throw new UnresolvedNamesException("UUID lookup was interrupted"); + } + } + + if (!namesToQuery.isEmpty()) { + throw new UnresolvedNamesException("Unable to resolve the names " + Joiner.on(", ").join(namesToQuery)); + } + + return domain; + } + + /** + * @deprecated was only used for Future transformation. Can be replaced with {@code region.getOwners()::addAll} (or getMembers). + */ + @Deprecated + public Function createAddAllFunction(final DefaultDomain target) { + return domain -> { + target.addAll(domain); + return domain; + }; + } + + /** + * @deprecated was only used for Future transformation. Can be replaced with {@code region.getOwners()::removeAll} (or getMembers). + */ + @Deprecated + public Function createRemoveAllFunction(final DefaultDomain target) { + return domain -> { + target.removeAll(domain); + return domain; + }; + } + + /** + * Try to parse a UUID locator from input. + * + * @param input the input + * @return a UUID or {@code null} if the input is not a UUID + */ + @Nullable + public static UUID parseUUID(String input) { + checkNotNull(input); + + try { + return UUID.fromString(UUIDs.addDashes(input.replaceAll("^uuid:", ""))); + } catch (IllegalArgumentException e) { + return null; + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/NormativeOrders.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/NormativeOrders.java new file mode 100644 index 000000000..56a4e85b7 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/NormativeOrders.java @@ -0,0 +1,144 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.util; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.FlagValueCalculator; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import javax.annotation.Nullable; +import java.util.*; + +/** + * Sorts a list of regions so that higher priority regions always take + * precedence over lower priority ones, and after sorting by priority, so + * child regions always take priority over their parent regions. + * + *

For example, if the regions are a, aa, aaa, aab, aac, b, ba, bc, where + * aa implies that the second 'a' is a child of the first 'a', the sorted + * order must reflect the following properties (where regions on the + * left of < appear before in the sorted list):

+ * + *
    + *
  • [aaa, aab, aac] < aa < a
  • + *
  • [ba, bc] < b
  • + *
+ * + *

In the case of "[aaa, aab, aac]," the relative order between these + * regions is unimportant as they all share the same parent (aaa). The + * following choices would be valid sorts:

+ * + *
    + *
  • aaa, aab, aac, aa, a, ba, bc, b
  • + *
  • aab, aaa, aac, aa, a, bc, ba, b
  • + *
  • bc, ba, b, aab, aaa, aac, aa, a
  • + *
  • aab, aaa, bc, aac, aa, ba, a, b
  • + *
+ * + *

These sorted lists are required for {@link FlagValueCalculator} and + * some implementations of {@link ApplicableRegionSet}.

+ */ +public final class NormativeOrders { + + private static final PriorityComparator PRIORITY_COMPARATOR = new PriorityComparator(); + + private NormativeOrders() { + } + + public static void sort(List regions) { + sortInto(Sets.newHashSet(regions), regions); + } + + public static List fromSet(Set regions) { + List sorted = Arrays.asList(new ProtectedRegion[regions.size()]); + sortInto(regions, sorted); + return sorted; + } + + private static void sortInto(Set regions, List sorted) { + List root = Lists.newArrayList(); + Map nodes = Maps.newHashMap(); + for (ProtectedRegion region : regions) { + addNode(nodes, root, region); + } + + int index = regions.size() - 1; + for (RegionNode node : root) { + while (node != null) { + if (regions.contains(node.region)) { + sorted.set(index, node.region); + index--; + } + node = node.next; + } + } + + Collections.sort(sorted, PRIORITY_COMPARATOR); + } + + private static RegionNode addNode(Map nodes, List root, ProtectedRegion region) { + RegionNode node = nodes.get(region); + if (node == null) { + node = new RegionNode(region); + nodes.put(region, node); + if (region.getParent() != null) { + addNode(nodes, root, region.getParent()).insertAfter(node); + } else { + root.add(node); + } + } + return node; + } + + private static class RegionNode { + @Nullable private RegionNode next; + private final ProtectedRegion region; + + private RegionNode(ProtectedRegion region) { + this.region = region; + } + + private void insertAfter(RegionNode node) { + if (this.next == null) { + this.next = node; + } else { + node.next = this.next; + this.next = node; + } + } + } + + private static class PriorityComparator implements Comparator { + @Override + public int compare(ProtectedRegion o1, ProtectedRegion o2) { + if (o1.getPriority() > o2.getPriority()) { + return -1; + } else if (o1.getPriority() < o2.getPriority()) { + return 1; + } else { + return 0; + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java new file mode 100644 index 000000000..7c23a148b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/RegionCollectionConsumer.java @@ -0,0 +1,70 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.util; + +import com.google.common.base.Predicate; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.Collection; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * A consumer predicate that adds regions to a collection. + * + *

This class can also add the parents of regions that are visited + * to the collection, although it may result in duplicates in the collection + * if the collection is not a set.

+ */ +public class RegionCollectionConsumer implements Predicate { + + private final Collection collection; + private final boolean addParents; + + /** + * Create a new instance. + * + * @param collection the collection to add regions to + * @param addParents true to also add the parents to the collection + */ + public RegionCollectionConsumer(Collection collection, boolean addParents) { + checkNotNull(collection); + + this.collection = collection; + this.addParents = addParents; + } + + @Override + public boolean apply(ProtectedRegion region) { + collection.add(region); + + if (addParents) { + ProtectedRegion parent = region.getParent(); + + while (parent != null) { + collection.add(parent); + parent = parent.getParent(); + } + } + + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java new file mode 100644 index 000000000..c2a287775 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/UnresolvedNamesException.java @@ -0,0 +1,41 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.util; + +/** + * Thrown when there are unresolved names. + */ +public class UnresolvedNamesException extends Exception { + + public UnresolvedNamesException() { + } + + public UnresolvedNamesException(String message) { + super(message); + } + + public UnresolvedNamesException(String message, Throwable cause) { + super(message, cause); + } + + public UnresolvedNamesException(Throwable cause) { + super(cause); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/WorldEditRegionConverter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/WorldEditRegionConverter.java new file mode 100644 index 000000000..ee095b167 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/protection/util/WorldEditRegionConverter.java @@ -0,0 +1,75 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.util; + +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Polygonal2DRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.regions.selector.Polygonal2DRegionSelector; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +/** + * A helper class to convert regions from WorldGuard to WorldEdit + */ +public final class WorldEditRegionConverter { + private WorldEditRegionConverter() { + + } + + /** + * Converts a ProtectedRegion to a WorldEdit Region, otherwise null if + * the ProtectedRegion can't be converted to a RegionSelector. + * + * @param region the WorldGuard region + * @return the WorldEdit Region + */ + public static Region convertToRegion(ProtectedRegion region) { + if (region instanceof ProtectedCuboidRegion) { + return new CuboidRegion(null, region.getMinimumPoint(), region.getMaximumPoint()); + } + if (region instanceof ProtectedPolygonalRegion) { + return new Polygonal2DRegion(null, region.getPoints(), + region.getMinimumPoint().getY(), region.getMaximumPoint().getY()); + } + return null; + } + + /** + * Converts a ProtectedRegion to a WorldEdit RegionSelector, otherwise null if + * the ProtectedRegion can't be converted to a RegionSelector. + * + * @param region the WorldGuard region + * @return the WorldEdit Region + */ + public static RegionSelector convertToSelector(ProtectedRegion region) { + if (region instanceof ProtectedCuboidRegion) { + return new CuboidRegionSelector(null, region.getMinimumPoint(), region.getMaximumPoint()); + } + if (region instanceof ProtectedPolygonalRegion) { + return new Polygonal2DRegionSelector(null, region.getPoints(), + region.getMinimumPoint().getY(), region.getMaximumPoint().getY()); + } + return null; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/AbstractSessionManager.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/AbstractSessionManager.java new file mode 100644 index 000000000..19618d5d2 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/AbstractSessionManager.java @@ -0,0 +1,217 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.session.handler.EntryFlag; +import com.sk89q.worldguard.session.handler.ExitFlag; +import com.sk89q.worldguard.session.handler.FarewellFlag; +import com.sk89q.worldguard.session.handler.FeedFlag; +import com.sk89q.worldguard.session.handler.GameModeFlag; +import com.sk89q.worldguard.session.handler.GodMode; +import com.sk89q.worldguard.session.handler.GreetingFlag; +import com.sk89q.worldguard.session.handler.Handler; +import com.sk89q.worldguard.session.handler.HealFlag; +import com.sk89q.worldguard.session.handler.InvincibilityFlag; +import com.sk89q.worldguard.session.handler.NotifyEntryFlag; +import com.sk89q.worldguard.session.handler.NotifyExitFlag; +import com.sk89q.worldguard.session.handler.TimeLockFlag; +import com.sk89q.worldguard.session.handler.WaterBreathing; +import com.sk89q.worldguard.session.handler.WeatherLockFlag; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.BiPredicate; +import java.util.logging.Level; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class AbstractSessionManager implements SessionManager { + + public static final int RUN_DELAY = 20; + public static final long SESSION_LIFETIME = 10; + + private static final BiPredicate BYPASS_PERMISSION_TEST = (world, player) -> { + return player.hasPermission("worldguard.region.bypass." + world.getName()); + }; + + private final LoadingCache bypassCache = CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(2, TimeUnit.SECONDS) + .build(CacheLoader.from(tuple -> BYPASS_PERMISSION_TEST.test(tuple.getWorld(), tuple.getPlayer()))); + + private final LoadingCache sessions = CacheBuilder.newBuilder() + .expireAfterAccess(SESSION_LIFETIME, TimeUnit.MINUTES) + .build(CacheLoader.from(key -> + createSession(key.playerRef.get()))); + + private boolean hasCustom = false; + // + private Map, Handler.Factory> wrappedHandlers = new HashMap<>(); + private List> handlers = new LinkedList<>(); + + private static final List> defaultHandlers = new LinkedList<>(); + + static { + Handler.Factory[] factories = { + HealFlag.FACTORY, + FeedFlag.FACTORY, + NotifyEntryFlag.FACTORY, + NotifyExitFlag.FACTORY, + EntryFlag.FACTORY, + ExitFlag.FACTORY, + FarewellFlag.FACTORY, + GreetingFlag.FACTORY, + GameModeFlag.FACTORY, + InvincibilityFlag.FACTORY, + TimeLockFlag.FACTORY, + WeatherLockFlag.FACTORY, + GodMode.FACTORY, + WaterBreathing.FACTORY + }; + defaultHandlers.addAll(Arrays.asList(factories)); + } + + protected AbstractSessionManager() { + handlers.addAll(defaultHandlers); + } + + @Override + public boolean customHandlersRegistered() { + return hasCustom; + } + + protected Handler.Factory wrapForRegistration(Handler.Factory factory) { + return factory; + } + + @Override + public boolean registerHandler(Handler.Factory factory, @Nullable Handler.Factory after) { + if (factory == null) return false; + WorldGuard.logger.log(Level.INFO, "Registering session handler " + + factory.getClass().getEnclosingClass().getName()); + hasCustom = true; + Handler.Factory wrappedFactory = wrapForRegistration(factory); + if (after == null) { + handlers.add(wrappedFactory); + } else { + Handler.Factory wrappedAfter = wrappedHandlers.get(after); + int index = handlers.indexOf(wrappedAfter != null ? wrappedAfter : after); + if (index == -1) return false; + + handlers.add(index + 1, factory); // shifts "after" right one, and everything after "after" right one + } + wrappedHandlers.put(factory, wrappedFactory); + return true; + } + + @Override + public boolean unregisterHandler(Handler.Factory factory) { + if (defaultHandlers.contains(factory)) { + WorldGuard.logger.log(Level.WARNING, "Someone is unregistering a default WorldGuard handler: " + + factory.getClass().getEnclosingClass().getName() + ". This may cause parts of WorldGuard to stop functioning"); + } else { + WorldGuard.logger.log(Level.INFO, "Unregistering session handler " + + factory.getClass().getEnclosingClass().getName()); + factory = wrappedHandlers.remove(factory); + } + return handlers.remove(factory); + } + + @Override + public boolean hasBypass(LocalPlayer player, World world) { + Session sess = getIfPresent(player); + if (sess == null || sess.hasBypassDisabled()) { + return false; + } + + if (WorldGuard.getInstance().getPlatform().getGlobalStateManager().disablePermissionCache) { + return BYPASS_PERMISSION_TEST.test(world, player); + } + + return bypassCache.getUnchecked(new WorldPlayerTuple(world, player)); + } + + @Override + public void resetState(LocalPlayer player) { + checkNotNull(player, "player"); + @Nullable Session session = sessions.getIfPresent(new CacheKey(player)); + if (session != null) { + session.resetState(player); + } + } + + @Override + @Nullable + public Session getIfPresent(LocalPlayer player) { + return sessions.getIfPresent(new CacheKey(player)); + } + + @Override + public Session get(LocalPlayer player) { + return sessions.getUnchecked(new CacheKey(player)); + } + + @Override + public Session createSession(LocalPlayer player) { + Session session = new Session(this); + for (Handler.Factory factory : handlers) { + session.register(factory.create(session)); + } + session.initialize(player); + return session; + } + + protected static final class CacheKey { + final WeakReference playerRef; + final UUID uuid; + + CacheKey(LocalPlayer player) { + playerRef = new WeakReference<>(player); + uuid = player.getUniqueId(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CacheKey cacheKey = (CacheKey) o; + return uuid.equals(cacheKey.uuid); + + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/MoveType.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/MoveType.java new file mode 100644 index 000000000..8522e825b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/MoveType.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session; + +/** + * Types of movements. + * + *

Used with Session#testMoveTo(Location, MoveType).

+ */ +public enum MoveType { + + RESPAWN(false, true), + EMBARK(true, false), + MOVE(true, false), + GLIDE(true, false), + SWIM(true, false), + TELEPORT(true, true), + RIDE(true, false), + OTHER_NON_CANCELLABLE(false, false), + OTHER_CANCELLABLE(true, false); + + private final boolean cancellable; + private final boolean teleport; + + MoveType(boolean cancellable, boolean teleport) { + this.cancellable = cancellable; + this.teleport = teleport; + } + + public boolean isCancellable() { + return cancellable; + } + + public boolean isTeleport() { + return teleport; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/Session.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/Session.java new file mode 100644 index 000000000..b9109c8d6 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/Session.java @@ -0,0 +1,246 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.ConfigurationManager; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.RegionQuery; +import com.sk89q.worldguard.session.handler.Handler; +import com.sk89q.worldguard.util.Locations; + +import java.util.HashMap; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Keeps session information on a player. + */ +public class Session { + + private final SessionManager manager; + private boolean disableBypass; + private final HashMap, Handler> handlers = Maps.newLinkedHashMap(); + private Location lastValid; + private Set lastRegionSet; + private final AtomicBoolean needRefresh = new AtomicBoolean(false); + + /** + * Create a new session. + * + * @param manager The session manager + */ + public Session(SessionManager manager) { + checkNotNull(manager, "manager"); + this.manager = manager; + } + + /** + * Register a new handler. + * + * @param handler A new handler + */ + public void register(Handler handler) { + handlers.put(handler.getWrappedHandler().getClass(), handler); + } + + /** + * Get the session manager. + * + * @return The session manager + */ + public SessionManager getManager() { + return manager; + } + + /** + * Get a handler by class, if has been registered. + * + * @param type The type of handler + * @param The type of handler + * @return A handler instance, otherwise null + */ + @Nullable + @SuppressWarnings("unchecked") + public T getHandler(Class type) { + return (T) handlers.get(type).getWrappedHandler(); + } + + /** + * Initialize the session. + * + * @param player The player + */ + public void initialize(LocalPlayer player) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + Location location = player.getLocation(); + ApplicableRegionSet set = query.getApplicableRegions(location); + + lastValid = location; + lastRegionSet = set.getRegions(); + ConfigurationManager cfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager(); + disableBypass = cfg.disableDefaultBypass; + if (cfg.announceBypassStatus && player.hasPermission("worldguard.region.toggle-bypass")) { + player.printInfo(TextComponent.of( + "You are " + (disableBypass ? "not" : "") + " bypassing region protection. " + + "You can toggle this with /rg bypass", TextColor.DARK_PURPLE)); + } + + + for (Handler handler : handlers.values()) { + handler.initialize(player, location, set); + } + } + + /** + * Tick the session. + * + * @param player The player + */ + public void tick(LocalPlayer player) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + Location location = player.getLocation(); + ApplicableRegionSet set = query.getApplicableRegions(location); + + for (Handler handler : handlers.values()) { + handler.tick(player, set); + } + } + + /** + * Re-initialize the session. + * + * @param player The player + */ + public void resetState(LocalPlayer player) { + initialize(player); + needRefresh.set(true); + } + + /** + * Test whether the session has invincibility enabled. + * + * @return Whether invincibility is enabled + */ + public boolean isInvincible(LocalPlayer player) { + boolean invincible = false; + + for (Handler handler : handlers.values()) { + State state = handler.getInvincibility(player); + if (state != null) { + switch (state) { + case DENY: return false; + case ALLOW: invincible = true; + } + } + } + + return invincible; + } + + /** + * Test movement to the given location. + * + * @param player The player + * @param to The new location + * @param moveType The type of move + * @return The overridden location, if the location is being overridden + * @see #testMoveTo(LocalPlayer, Location, MoveType, boolean) For an explanation + */ + @Nullable + public Location testMoveTo(LocalPlayer player, Location to, MoveType moveType) { + return testMoveTo(player, to, moveType, false); + } + + /** + * Test movement to the given location. + * + *

If a non-null {@link Location} is returned, the player should be + * at that location instead of where the player has tried to move to.

+ * + *

If the {@code moveType} is cancellable + * ({@link MoveType#isCancellable()}, then the last valid location will + * be set to the given one.

+ * + * @param player The player + * @param to The new location + * @param moveType The type of move + * @param forced Whether to force a check + * @return The overridden location, if the location is being overridden + */ + @Nullable + public Location testMoveTo(LocalPlayer player, Location to, MoveType moveType, boolean forced) { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + + if (!forced && needRefresh.getAndSet(false)) { + forced = true; + } + + if (forced || Locations.isDifferentBlock(lastValid, to)) { + ApplicableRegionSet toSet = query.getApplicableRegions(to); + + for (Handler handler : handlers.values()) { + if (!handler.testMoveTo(player, lastValid, to, toSet, moveType) && moveType.isCancellable()) { + return lastValid; + } + } + + Set entered = Sets.difference(toSet.getRegions(), lastRegionSet); + Set exited = Sets.difference(lastRegionSet, toSet.getRegions()); + + for (Handler handler : handlers.values()) { + if (!handler.onCrossBoundary(player, lastValid, to, toSet, entered, exited, moveType) && moveType.isCancellable()) { + return lastValid; + } + } + + lastValid = to; + lastRegionSet = toSet.getRegions(); + } + + return null; + } + + /** + * @return true if the owner of this session should not bypass protection, even if they have bypass permissions + */ + public boolean hasBypassDisabled() { + return disableBypass; + } + + /** + * Toggle bypass disabling for this session. + * @param disabled true to disable region bypass + */ + public void setBypassDisabled(boolean disabled) { + disableBypass = disabled; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/SessionManager.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/SessionManager.java new file mode 100644 index 000000000..bd34f7ec2 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/SessionManager.java @@ -0,0 +1,121 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session; + +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.session.handler.Handler; + +import java.util.Collection; + +import javax.annotation.Nullable; + +public interface SessionManager { + + /** + * Check whether a player has the region bypass permission. + * + *

The return value may be cached for a few seconds.

+ * + * @param player The player + * @param world The world + * @return A value + */ + boolean hasBypass(LocalPlayer player, World world); + + /** + * Re-initialize handlers and clear "last position," "last state," etc. + * information for all players. + */ + void resetAllStates(); + + /** + * Re-initialize handlers and clear "last position," "last state," etc. + * information. + * + * @param player The player + */ + void resetState(LocalPlayer player); + + /** + * @return true if custom handlers are or were at some point registered, false otherwise + */ + boolean customHandlersRegistered(); + + /** + * Register a handler with the BukkitSessionManager. + * + * You may specify another handler class to ensure your handler is always registered after that class. + * If that class is not already registered, this method will return false. + * + * For example, flags that always act on a player in a region (like HealFlag and FeedFlag) + * should be registered earlier, whereas flags that only take effect when a player leaves the region (like + * FarewellFlag and GreetingFlag) should be registered after the ExitFlag.Factory.class handler factory. + * + * @param factory a factory which takes a session and returns an instance of your handler + * @param after the handler factory to insert the first handler after, to ensure a specific order when creating new sessions + * + * @return {@code true} (as specified by {@link Collection#add}) + * {@code false} if after is not registered, or factory is null + */ + boolean registerHandler(Handler.Factory factory, @Nullable Handler.Factory after); + + /** + * Unregister a handler. + * + * This will prevent it from being added to newly created sessions only. Existing + * sessions with the handler will continue to use it. + * + * Will return false if the handler was not registered to begin with. + * + * @param factory the handler factory to unregister + * @return true if the handler was registered and is now unregistered, false otherwise + */ + boolean unregisterHandler(Handler.Factory factory); + + /** + * Create a session for a player. + * + * @param player The player + * @return The new session + */ + Session createSession(LocalPlayer player); + + /** + * Get a player's session, if one exists. + * + * @param player The player + * @return The session + */ + @Nullable Session getIfPresent(LocalPlayer player); + + /** + * Get a player's session. A session will be created if there is no + * existing session for the player. + * + *

This method can only be called from the main thread. While the + * session manager itself is thread-safe, some of the handlers may + * require initialization that requires the server main thread.

+ * + * @param player The player to get a session for + * @return The {@code player}'s session + */ + Session get(LocalPlayer player); +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/WorldPlayerTuple.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/WorldPlayerTuple.java new file mode 100644 index 000000000..8996e147e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/WorldPlayerTuple.java @@ -0,0 +1,63 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session; + +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; + +public class WorldPlayerTuple { + + private final World world; + private final LocalPlayer player; + + public WorldPlayerTuple(World world, LocalPlayer player) { + this.world = world; + this.player = player; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + WorldPlayerTuple that = (WorldPlayerTuple) o; + + if (!player.equals(that.player)) return false; + if (!world.equals(that.world)) return false; + + return true; + } + + public LocalPlayer getPlayer() { + return player; + } + + public World getWorld() { + return world; + } + + @Override + public int hashCode() { + int result = world.hashCode(); + result = 31 * result + player.hashCode(); + return result; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/EntryFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/EntryFlag.java new file mode 100644 index 000000000..7cedea589 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/EntryFlag.java @@ -0,0 +1,71 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.commands.CommandUtils; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +import java.util.Set; + +public class EntryFlag extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public EntryFlag create(Session session) { + return new EntryFlag(session); + } + } + + private static final long MESSAGE_THRESHOLD = 1000 * 2; + private long lastMessage; + + public EntryFlag(Session session) { + super(session); + } + + @Override + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Set entered, Set exited, MoveType moveType) { + boolean allowed = toSet.testState(player, Flags.ENTRY); + + if (!getSession().getManager().hasBypass(player, (World) to.getExtent()) && !allowed && moveType.isCancellable()) { + String message = toSet.queryValue(player, Flags.ENTRY_DENY_MESSAGE); + long now = System.currentTimeMillis(); + + if ((now - lastMessage) > MESSAGE_THRESHOLD && message != null && !message.isEmpty()) { + player.printRaw(CommandUtils.replaceColorMacros(message)); + lastMessage = now; + } + + return false; + } else { + return true; + } + } + + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/ExitFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/ExitFlag.java new file mode 100644 index 000000000..dcae5390a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/ExitFlag.java @@ -0,0 +1,113 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.commands.CommandUtils; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +public class ExitFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public ExitFlag create(Session session) { + return new ExitFlag(session); + } + } + + private static final long MESSAGE_THRESHOLD = 1000 * 2; + private String storedMessage; + private boolean exitViaTeleport = false; + private long lastMessage; + + public ExitFlag(Session session) { + super(session, Flags.EXIT); + } + + private void update(LocalPlayer localPlayer, ApplicableRegionSet set, boolean allowed) { + if (!allowed) { + storedMessage = set.queryValue(localPlayer, Flags.EXIT_DENY_MESSAGE); + exitViaTeleport = set.testState(localPlayer, Flags.EXIT_VIA_TELEPORT); + } + } + + private void sendMessage(LocalPlayer player) { + long now = System.currentTimeMillis(); + + if ((now - lastMessage) > MESSAGE_THRESHOLD && storedMessage != null && !storedMessage.isEmpty()) { + player.printRaw(CommandUtils.replaceColorMacros(storedMessage)); + lastMessage = now; + } + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, State value) { + update(player, set, StateFlag.test(value)); + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, State currentValue, State lastValue, MoveType moveType) { + if (getSession().getManager().hasBypass(player, (World) from.getExtent())) { + return true; + } + + boolean lastAllowed = StateFlag.test(lastValue); + boolean allowed = StateFlag.test(currentValue); + + if (allowed && !lastAllowed && !(moveType.isTeleport() && exitViaTeleport) && moveType.isCancellable()) { + Boolean override = toSet.queryValue(player, Flags.EXIT_OVERRIDE); + if (override == null || !override) { + sendMessage(player); + return false; + } + } + + update(player, toSet, allowed); + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, State lastValue, MoveType moveType) { + if (getSession().getManager().hasBypass(player, (World) from.getExtent())) { + return true; + } + + boolean lastAllowed = StateFlag.test(lastValue); + + if (!lastAllowed && moveType.isCancellable()) { + Boolean override = toSet.queryValue(player, Flags.EXIT_OVERRIDE); + if (override == null || !override) { + sendMessage(player); + return false; + } + } + + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FarewellFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FarewellFlag.java new file mode 100644 index 000000000..e47c48b41 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FarewellFlag.java @@ -0,0 +1,97 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.google.common.collect.Sets; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.util.MessagingUtil; + +import java.util.Collections; +import java.util.Set; +import java.util.function.BiConsumer; + +public class FarewellFlag extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public FarewellFlag create(Session session) { + return new FarewellFlag(session); + } + } + + private Set lastMessageStack = Collections.emptySet(); + private Set lastTitleStack = Collections.emptySet(); + + public FarewellFlag(Session session) { + super(session); + } + + private Set getMessages(LocalPlayer player, ApplicableRegionSet set, Flag flag) { + return Sets.newLinkedHashSet(set.queryAllValues(player, flag)); + } + + @Override + public void initialize(LocalPlayer player, Location current, ApplicableRegionSet set) { + lastMessageStack = getMessages(player, set, Flags.FAREWELL_MESSAGE); + lastTitleStack = getMessages(player, set, Flags.FAREWELL_TITLE); + } + + @Override + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, + Set entered, Set exited, MoveType moveType) { + + lastMessageStack = collectAndSend(player, toSet, Flags.FAREWELL_MESSAGE, lastMessageStack, MessagingUtil::sendStringToChat); + lastTitleStack = collectAndSend(player, toSet, Flags.FAREWELL_TITLE, lastTitleStack, MessagingUtil::sendStringToTitle); + + return true; + } + + private Set collectAndSend(LocalPlayer player, ApplicableRegionSet toSet, Flag flag, + Set stack, BiConsumer msgFunc) { + Set messages = getMessages(player, toSet, flag); + + if (!messages.isEmpty()) { + // Due to flag priorities, we have to collect the lower + // priority flag values separately + for (ProtectedRegion region : toSet) { + String message = region.getFlag(flag); + if (message != null) { + messages.add(message); + } + } + } + + for (String message : stack) { + if (!messages.contains(message)) { + msgFunc.accept(player, message); + break; + } + } + return messages; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FeedFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FeedFlag.java new file mode 100644 index 000000000..e34201229 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FeedFlag.java @@ -0,0 +1,89 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.world.gamemode.GameModes; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.Session; + +public class FeedFlag extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public FeedFlag create(Session session) { + return new FeedFlag(session); + } + } + + private long lastFeed = 0; + + public FeedFlag(Session session) { + super(session); + } + + @Override + public void tick(LocalPlayer player, ApplicableRegionSet set) { + long now = System.currentTimeMillis(); + + Integer feedAmount = set.queryValue(player, Flags.FEED_AMOUNT); + Integer feedDelay = set.queryValue(player, Flags.FEED_DELAY); + Integer minHunger = set.queryValue(player, Flags.MIN_FOOD); + Integer maxHunger = set.queryValue(player, Flags.MAX_FOOD); + + if (feedAmount == null || feedDelay == null || feedAmount == 0 || feedDelay < 0) { + return; + } + if (feedAmount < 0 + && (getSession().isInvincible(player) + || (player.getGameMode() != GameModes.SURVIVAL && player.getGameMode() != GameModes.ADVENTURE))) { + // don't starve invincible players + return; + } + if (minHunger == null) { + minHunger = 0; + } + if (maxHunger == null) { + maxHunger = 20; + } + + // Apply a cap to prevent possible exceptions + minHunger = Math.min(20, minHunger); + maxHunger = Math.min(20, maxHunger); + + if (player.getFoodLevel() >= maxHunger && feedAmount > 0) { + return; + } + + if (feedDelay <= 0) { + player.setFoodLevel(feedAmount > 0 ? maxHunger : minHunger); + player.setSaturation(player.getFoodLevel()); + lastFeed = now; + } else if (now - lastFeed > feedDelay * 1000) { + // clamp health between minimum and maximum + player.setFoodLevel(Math.min(maxHunger, Math.max(minHunger, player.getFoodLevel() + feedAmount))); + player.setSaturation(player.getFoodLevel()); + lastFeed = now; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FlagValueChangeHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FlagValueChangeHandler.java new file mode 100644 index 000000000..acc87bbca --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/FlagValueChangeHandler.java @@ -0,0 +1,77 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +import java.util.Set; + +public abstract class FlagValueChangeHandler extends Handler { + + private final Flag flag; + private T lastValue; + + protected FlagValueChangeHandler(Session session, Flag flag) { + super(session); + this.flag = flag; + } + + @Override + public final void initialize(LocalPlayer player, Location current, ApplicableRegionSet set) { + lastValue = set.queryValue(player, flag); + onInitialValue(player, set, lastValue); + } + + @Override + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Set entered, Set exited, MoveType moveType) { + if (entered.isEmpty() && exited.isEmpty() + && from.getExtent().equals(to.getExtent())) { // sets don't include global regions - check if those changed + return true; // no changes to flags if regions didn't change + } + + T currentValue = toSet.queryValue(player, flag); + boolean allowed = true; + + if (currentValue == null && lastValue != null) { + allowed = onAbsentValue(player, from, to, toSet, lastValue, moveType); + } else if (currentValue != null && currentValue != lastValue) { + allowed = onSetValue(player, from, to, toSet, currentValue, lastValue, moveType); + } + + if (allowed) { + lastValue = currentValue; + } + + return allowed; + } + + protected abstract void onInitialValue(LocalPlayer player, ApplicableRegionSet set, T value); + + protected abstract boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, T currentValue, T lastValue, MoveType moveType); + + protected abstract boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, T lastValue, MoveType moveType); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GameModeFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GameModeFlag.java new file mode 100644 index 000000000..4dbf0ab44 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GameModeFlag.java @@ -0,0 +1,93 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +import javax.annotation.Nullable; + +public class GameModeFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public GameModeFlag create(Session session) { + return new GameModeFlag(session); + } + } + + private GameMode originalGameMode; + private GameMode setGameMode; + + public GameModeFlag(Session session) { + super(session, Flags.GAME_MODE); + } + + public GameMode getOriginalGameMode() { + return originalGameMode; + } + + public GameMode getSetGameMode() { + return setGameMode; + } + + private void updateGameMode(LocalPlayer player, @Nullable GameMode newValue, World world) { + if (!getSession().getManager().hasBypass(player, world) && newValue != null) { + if (player.getGameMode() != newValue) { + originalGameMode = player.getGameMode(); + player.setGameMode(newValue); + } else if (originalGameMode == null) { + originalGameMode = WorldGuard.getInstance().getPlatform().getDefaultGameMode(); + } + } else { + if (originalGameMode != null) { + GameMode mode = originalGameMode; + originalGameMode = null; + player.setGameMode(mode); + } + } + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, GameMode value) { + updateGameMode(player, value, player.getWorld()); + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, GameMode currentValue, GameMode lastValue, MoveType moveType) { + updateGameMode(player, currentValue, (World) to.getExtent()); + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, GameMode lastValue, MoveType moveType) { + updateGameMode(player, null, (World) player.getExtent()); + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GodMode.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GodMode.java new file mode 100644 index 000000000..04dc796f2 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GodMode.java @@ -0,0 +1,83 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.session.Session; + +import javax.annotation.Nullable; + +public class GodMode extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public GodMode create(Session session) { + return new GodMode(session); + } + } + + private boolean godMode; + + public GodMode(Session session) { + super(session); + } + + public boolean hasGodMode(LocalPlayer player) { + // TODO +// if (getPlugin().getGlobalStateManager().hasCommandBookGodMode()) { +// GodComponent god = CommandBook.inst().getComponentManager().getComponent(GodComponent.class); +// if (god != null) { +// return god.hasGodMode(player); +// } +// } + + return godMode; + } + + public void setGodMode(LocalPlayer player, boolean godMode) { +// if (getPlugin().getGlobalStateManager().hasCommandBookGodMode()) { +// GodComponent god = CommandBook.inst().getComponentManager().getComponent(GodComponent.class); +// if (god != null) { +// god.enableGodMode(player); +// } +// } + + this.godMode = godMode; + } + + @Nullable + @Override + public State getInvincibility(LocalPlayer player) { + return hasGodMode(player) ? State.ALLOW : null; + } + + public static boolean set(LocalPlayer player, Session session, boolean value) { + GodMode godMode = session.getHandler(GodMode.class); + if (godMode != null) { + godMode.setGodMode(player, value); + return true; + } else{ + return false; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GreetingFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GreetingFlag.java new file mode 100644 index 000000000..fd8b946b2 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/GreetingFlag.java @@ -0,0 +1,93 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.google.common.collect.Sets; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flag; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.util.MessagingUtil; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.function.BiConsumer; + +public class GreetingFlag extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public GreetingFlag create(Session session) { + return new GreetingFlag(session); + } + } + + private Set lastMessageStack = Collections.emptySet(); + private Set lastTitleStack = Collections.emptySet(); + + public GreetingFlag(Session session) { + super(session); + } + + private Set getMessages(LocalPlayer player, ApplicableRegionSet set, Flag flag) { + return Sets.newLinkedHashSet(set.queryAllValues(player, flag)); + } + + @Override + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, + Set entered, Set exited, MoveType moveType) { + lastMessageStack = sendAndCollect(player, toSet, Flags.GREET_MESSAGE, lastMessageStack, MessagingUtil::sendStringToChat); + lastTitleStack = sendAndCollect(player, toSet, Flags.GREET_TITLE, lastTitleStack, MessagingUtil::sendStringToTitle); + return true; + } + + private Set sendAndCollect(LocalPlayer player, ApplicableRegionSet toSet, Flag flag, + Set stack, BiConsumer msgFunc) { + Collection messages = getMessages(player, toSet, flag); + + for (String message : messages) { + if (!stack.contains(message)) { + msgFunc.accept(player, message); + break; + } + } + + stack = Sets.newHashSet(messages); + + if (!stack.isEmpty()) { + // Due to flag priorities, we have to collect the lower + // priority flag values separately + for (ProtectedRegion region : toSet) { + String message = region.getFlag(flag); + if (message != null) { + stack.add(message); + } + } + } + + return stack; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/Handler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/Handler.java new file mode 100644 index 000000000..def30c466 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/Handler.java @@ -0,0 +1,148 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.session.SessionManager; + +import javax.annotation.Nullable; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Stores session data and possibly acts on it. + */ +public abstract class Handler { + + public abstract static class Factory { + public abstract T create(Session session); + } + + private final Session session; + + /** + * Create a new handler. + * + * @param session The session + */ + protected Handler(Session session) { + checkNotNull(session, "session"); + this.session = session; + } + + /** + * Get the session. + * + * @return The session + */ + public Session getSession() { + return session; + } + + /** + * Called when the session is first being created or + * {@code /wg flushstates} is used. + * + * @param player The player + * @param current The player's current location + * @param set The regions for the current location + */ + public void initialize(LocalPlayer player, Location current, ApplicableRegionSet set) { + } + + /** + * Called when {@link Session#testMoveTo(LocalPlayer, Location, MoveType)} is called. + * + *

If this method returns {@code false}, then no other handlers + * will be run (for this move attempt).

+ * + * @param player The player + * @param from The previous, valid, location + * @param to The new location to test + * @param toSet The regions for the new location + * @param moveType The type of movement + * @return Whether the movement should be allowed + */ + public boolean testMoveTo(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, MoveType moveType) { + return true; + } + + /** + * Called when a player has moved into a new location. + * + *

This is called only if the move test + * ({@link Session#testMoveTo(LocalPlayer, Location, MoveType)}) was successful.

+ * + *

If this method returns {@code false}, then no other handlers + * will be run (for this move attempt).

+ * + * @param player The player + * @param from The previous, valid, location + * @param to The new location to test + * @param toSet The regions for the new location + * @param entered The list of regions that have been entered + * @param exited The list of regions that have been left + * @param moveType The type of move + * @return Whether the movement should be allowed + */ + public boolean onCrossBoundary(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, + Set entered, Set exited, MoveType moveType) { + return true; + } + + /** + * Called periodically (at least once every second) by + * {@link SessionManager} in the server's main thread. + * + * @param player The player + * @param set The regions for the player's current location + */ + public void tick(LocalPlayer player, ApplicableRegionSet set) { + } + + /** + * Return whether the player should be invincible. + * + *

{@link State#DENY} can be returned to prevent invincibility + * even if another handler permits it.

+ * + * @param player The player + * @return Invincibility state + */ + @Nullable + public State getInvincibility(LocalPlayer player) { + return null; + } + + /** + * Get the handler wrapped by this handler object, if applicable, or just return this if no handler is wrapped. + * @return any wrapped handler, or this handler itself + */ + public Handler getWrappedHandler() { + return this; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/HealFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/HealFlag.java new file mode 100644 index 000000000..8590c45a3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/HealFlag.java @@ -0,0 +1,91 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.world.gamemode.GameModes; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.Session; + +public class HealFlag extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public HealFlag create(Session session) { + return new HealFlag(session); + } + } + + private long lastHeal = 0; + + public HealFlag(Session session) { + super(session); + } + + @Override + public void tick(LocalPlayer player, ApplicableRegionSet set) { + if (player.getHealth() <= 0) { + return; + } + + long now = System.currentTimeMillis(); + + Integer healAmount = set.queryValue(player, Flags.HEAL_AMOUNT); + Integer healDelay = set.queryValue(player, Flags.HEAL_DELAY); + Double minHealth = set.queryValue(player, Flags.MIN_HEAL); + Double maxHealth = set.queryValue(player, Flags.MAX_HEAL); + + if (healAmount == null || healDelay == null || healAmount == 0 || healDelay < 0) { + return; + } + if (healAmount < 0 + && (getSession().isInvincible(player) + || (player.getGameMode() != GameModes.SURVIVAL && player.getGameMode() != GameModes.ADVENTURE))) { + // don't damage invincible players + return; + } + if (minHealth == null) { + minHealth = 0.0; + } + if (maxHealth == null) { + maxHealth = player.getMaxHealth(); + } + + // Apply a cap to prevent possible exceptions + minHealth = Math.min(player.getMaxHealth(), minHealth); + maxHealth = Math.min(player.getMaxHealth(), maxHealth); + + if (player.getHealth() >= maxHealth && healAmount > 0) { + return; + } + + if (healDelay <= 0) { + player.setHealth(healAmount > 0 ? maxHealth : minHealth); // this will insta-kill if the flag is unset + lastHeal = now; + } else if (now - lastHeal > healDelay * 1000) { + // clamp health between minimum and maximum + player.setHealth(Math.min(maxHealth, Math.max(minHealth, player.getHealth() + healAmount))); + lastHeal = now; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/InvincibilityFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/InvincibilityFlag.java new file mode 100644 index 000000000..79cc2efb4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/InvincibilityFlag.java @@ -0,0 +1,76 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +import javax.annotation.Nullable; + +public class InvincibilityFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public InvincibilityFlag create(Session session) { + return new InvincibilityFlag(session); + } + } + + @Nullable + private State invincibility; + + public InvincibilityFlag(Session session) { + super(session, Flags.INVINCIBILITY); + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, State value) { + invincibility = value; + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, State currentValue, State lastValue, MoveType moveType) { + invincibility = currentValue; + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, State lastValue, MoveType moveType) { + invincibility = null; + return true; + } + + @Override + @Nullable + public State getInvincibility(LocalPlayer player) { + if (invincibility == State.DENY && player.hasPermission("worldguard.god.override-regions")) { + return null; + } + + return invincibility; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyEntryFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyEntryFlag.java new file mode 100644 index 000000000..5202561f3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyEntryFlag.java @@ -0,0 +1,73 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.util.formatting.component.Notify; + +public class NotifyEntryFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public NotifyEntryFlag create(Session session) { + return new NotifyEntryFlag(session); + } + } + + public NotifyEntryFlag(Session session) { + super(session, Flags.NOTIFY_ENTER); + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, Boolean value) { + + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Boolean currentValue, Boolean lastValue, MoveType moveType) { + StringBuilder regionList = new StringBuilder(); + + for (ProtectedRegion region : toSet) { + if (regionList.length() != 0) { + regionList.append(", "); + } + + regionList.append(region.getId()); + } + + WorldGuard.getInstance().getPlatform().broadcastNotification(new Notify(player.getName(), " entered NOTIFY region: " + regionList).create()); + + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Boolean lastValue, MoveType moveType) { + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyExitFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyExitFlag.java new file mode 100644 index 000000000..7fd779b82 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/NotifyExitFlag.java @@ -0,0 +1,62 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; +import com.sk89q.worldguard.util.formatting.component.Notify; + +public class NotifyExitFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public NotifyExitFlag create(Session session) { + return new NotifyExitFlag(session); + } + } + + private Boolean notifiedForLeave = false; + + public NotifyExitFlag(Session session) { + super(session, Flags.NOTIFY_LEAVE); + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, Boolean value) { + + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Boolean currentValue, Boolean lastValue, MoveType moveType) { + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, Boolean lastValue, MoveType moveType) { + WorldGuard.getInstance().getPlatform().broadcastNotification(new Notify(player.getName(), " left NOTIFY region").create()); + return true; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/TimeLockFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/TimeLockFlag.java new file mode 100644 index 000000000..3090a9c7c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/TimeLockFlag.java @@ -0,0 +1,87 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +import javax.annotation.Nullable; +import java.util.regex.Pattern; + +public class TimeLockFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public TimeLockFlag create(Session session) { + return new TimeLockFlag(session); + } + } + + private long initialTime; + private boolean initialRelative; + + private static Pattern timePattern = Pattern.compile("([+\\-])?\\d+"); + + public TimeLockFlag(Session session) { + super(session, Flags.TIME_LOCK); + } + + private void updatePlayerTime(LocalPlayer player, @Nullable String value) { + if (value == null || !timePattern.matcher(value).matches()) { + // invalid input + return; + } + boolean relative = value.startsWith("+") || value.startsWith("-"); + long time = Long.parseLong(value); + player.setPlayerTime(time, relative); + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, String value) { + initialRelative = player.isPlayerTimeRelative(); + initialTime = player.getPlayerTimeOffset(); + updatePlayerTime(player, value); + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, String currentValue, String lastValue, MoveType moveType) { + if (lastValue == null) { + initialRelative = player.isPlayerTimeRelative(); + initialTime = player.getPlayerTimeOffset(); + } + updatePlayerTime(player, currentValue); + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, String lastValue, MoveType moveType) { + // in the case that time = 0 and relative = true, this is the same as resetPlayerTime + player.setPlayerTime(initialTime, initialRelative); + initialTime = 0L; + initialRelative = true; + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WaterBreathing.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WaterBreathing.java new file mode 100644 index 000000000..adbfd7beb --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WaterBreathing.java @@ -0,0 +1,59 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.session.Session; + +public class WaterBreathing extends Handler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public WaterBreathing create(Session session) { + return new WaterBreathing(session); + } + } + + public boolean waterBreathing; + + public WaterBreathing(Session session) { + super(session); + } + + public boolean hasWaterBreathing() { + return waterBreathing; + } + + public void setWaterBreathing(boolean waterBreathing) { + this.waterBreathing = waterBreathing; + } + + public static boolean set(LocalPlayer player, Session session, boolean value) { + WaterBreathing waterBreathing = session.getHandler(WaterBreathing.class); + if (waterBreathing != null) { + waterBreathing.setWaterBreathing(value); + return true; + } else{ + return false; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WeatherLockFlag.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WeatherLockFlag.java new file mode 100644 index 000000000..1e1eb56aa --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/session/handler/WeatherLockFlag.java @@ -0,0 +1,74 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.session.handler; + +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.weather.WeatherType; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.session.MoveType; +import com.sk89q.worldguard.session.Session; + +public class WeatherLockFlag extends FlagValueChangeHandler { + + public static final Factory FACTORY = new Factory(); + public static class Factory extends Handler.Factory { + @Override + public WeatherLockFlag create(Session session) { + return new WeatherLockFlag(session); + } + } + + private WeatherType initialWeather; + + public WeatherLockFlag(Session session) { + super(session, Flags.WEATHER_LOCK); + } + + @Override + protected void onInitialValue(LocalPlayer player, ApplicableRegionSet set, WeatherType value) { + initialWeather = player.getPlayerWeather(); + if (value != null) { + player.setPlayerWeather(value); + } + } + + @Override + protected boolean onSetValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, WeatherType currentValue, WeatherType lastValue, MoveType moveType) { + if (lastValue == null) { + initialWeather = player.getPlayerWeather(); + } + player.setPlayerWeather(currentValue); + return true; + } + + @Override + protected boolean onAbsentValue(LocalPlayer player, Location from, Location to, ApplicableRegionSet toSet, WeatherType lastValue, MoveType moveType) { + if (initialWeather != null) { + player.setPlayerWeather(initialWeather); + } else { + player.resetPlayerWeather(); + } + initialWeather = null; + return true; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java new file mode 100644 index 000000000..c7baaf770 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/ChangeTracked.java @@ -0,0 +1,42 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +/** + * An object that keeps track of a dirty flag that is set to true when changes + * are made to this object. + */ +public interface ChangeTracked { + + /** + * Tests whether changes have been made. + * + * @return true if changes have been made + */ + boolean isDirty(); + + /** + * Set whether changes have been made. + * + * @param dirty a new dirty state + */ + void setDirty(boolean dirty); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Entities.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Entities.java new file mode 100644 index 000000000..ab54f5c53 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Entities.java @@ -0,0 +1,48 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.entity.metadata.EntityProperties; + +public class Entities { + + private Entities() { + } + + /** + * Returns whether an entity should be removed for the halt activity mode. + * + * @param entity The entity + * @return true if it's to be removed + */ + public static boolean isIntensiveEntity(Entity entity) { + EntityProperties properties = entity.getFacet(EntityProperties.class); + return properties != null + && (properties.isItem() + || properties.isTNT() + || properties.isExperienceOrb() + || properties.isFallingBlock() + || (properties.isLiving() + && !(properties.isTamed()) + && !(properties.isPlayerDerived()) + && !properties.isArmorStand())); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Enums.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Enums.java new file mode 100644 index 000000000..48b92fa83 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Enums.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Helper methods for enums. + */ +public final class Enums { + + private Enums() { + } + + /** + * Search the given enum for a value that is equal to the one of the + * given values, searching in an ascending manner. + * + * @param enumType the enum type + * @param values the list of values + * @param the type of enum + * @return the found value or null + */ + @Nullable + public static > T findByValue(Class enumType, String... values) { + checkNotNull(enumType); + checkNotNull(values); + for (String val : values) { + try { + return Enum.valueOf(enumType, val); + } catch (IllegalArgumentException ignored) {} + } + return null; + } + + /** + * Search the given enum for a value that is equal to the one of the + * given values, searching in an ascending manner. + * + *

Some fuzzy matching of the provided values may be performed.

+ * + * @param enumType the enum type + * @param values the list of values + * @param the type of enum + * @return the found value or null + */ + @Nullable + public static > T findFuzzyByValue(Class enumType, String... values) { + checkNotNull(enumType); + checkNotNull(values); + for (String test : values) { + test = test.replace("_", ""); + for (T value : enumType.getEnumConstants()) { + if (value.name().replace("_", "").equalsIgnoreCase(test)) { + return value; + } + } + } + return null; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Locations.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Locations.java new file mode 100644 index 000000000..d0f4733c4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Locations.java @@ -0,0 +1,40 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.sk89q.worldedit.util.Location; + +public final class Locations { + + private Locations() { + } + + /** + * Tests whether two different locations are in two different blocks. + * + * @param a The first location + * @param b The second location + * @return Whether the two locations are two different blocks + */ + public static boolean isDifferentBlock(Location a, Location b) { + return a.getBlockX() != b.getBlockX() || a.getBlockY() != b.getBlockY() || a.getBlockZ() != b.getBlockZ(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MathUtils.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MathUtils.java new file mode 100644 index 000000000..b07a5637d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MathUtils.java @@ -0,0 +1,67 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +/** + * Math-related utilities. + */ +public final class MathUtils { + + private MathUtils() { + } + + private static void checkNoOverflow(boolean condition) { + if (!condition) { + throw new ArithmeticException("overflow"); + } + } + + /** + * Returns the product of {@code a} and {@code b}, provided it does not overflow. + * + *

Borrowed from Google Guava since Bukkit uses an old version.

+ * + * @throws ArithmeticException if {@code a * b} overflows in signed {@code long} arithmetic + */ + public static long checkedMultiply(long a, long b) { + // Hacker's Delight, Section 2-12 + int leadingZeros = Long.numberOfLeadingZeros(a) + Long.numberOfLeadingZeros(~a) + + Long.numberOfLeadingZeros(b) + Long.numberOfLeadingZeros(~b); + /* + * If leadingZeros > Long.SIZE + 1 it's definitely fine, if it's < Long.SIZE it's definitely + * bad. We do the leadingZeros check to avoid the division below if at all possible. + * + * Otherwise, if b == Long.MIN_VALUE, then the only allowed values of a are 0 and 1. We take + * care of all a < 0 with their own check, because in particular, the case a == -1 will + * incorrectly pass the division check below. + * + * In all other cases, we check that either a is 0 or the result is consistent with division. + */ + if (leadingZeros > Long.SIZE + 1) { + return a * b; + } + checkNoOverflow(leadingZeros >= Long.SIZE); + checkNoOverflow(a >= 0 | b != Long.MIN_VALUE); + long result = a * b; + checkNoOverflow(a == 0 || result / a == b); + return result; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MessagingUtil.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MessagingUtil.java new file mode 100644 index 000000000..4c2b5ce28 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/MessagingUtil.java @@ -0,0 +1,52 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.commands.CommandUtils; + +public final class MessagingUtil { + + private MessagingUtil() { + } + + public static void sendStringToChat(LocalPlayer player, String message) { + String effective = CommandUtils.replaceColorMacros(message); + effective = WorldGuard.getInstance().getPlatform().getMatcher().replaceMacros(player, effective); + for (String mess : effective.replaceAll("\\\\n", "\n").split("\\n")) { + player.printRaw(mess); + } + } + + public static void sendStringToTitle(LocalPlayer player, String message) { + String[] parts = message.replaceAll("\\\\n", "\n").split("\\n", 2); + String title = CommandUtils.replaceColorMacros(parts[0]); + title = WorldGuard.getInstance().getPlatform().getMatcher().replaceMacros(player, title); + if (parts.length > 1) { + String subtitle = CommandUtils.replaceColorMacros(parts[1]); + subtitle = WorldGuard.getInstance().getPlatform().getMatcher().replaceMacros(player, subtitle); + player.sendTitle(title, subtitle); + } else { + player.sendTitle(title, null); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Normal.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Normal.java new file mode 100644 index 000000000..50d60d0fa --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/Normal.java @@ -0,0 +1,119 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import javax.annotation.Nullable; +import java.text.Normalizer; +import java.text.Normalizer.Form; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Normal names are strings that are considered equal after they have been + * normalized using Unicode's NFC form and made lowercase. + */ +public final class Normal { + + private final String name; + @Nullable + private final String normal; + + /** + * Create a new instance. + * + * @param name a new instance + */ + private Normal(String name) { + checkNotNull(name); + + this.name = name; + String normal = normalize(name); + if (!normal.equals(name)) { // Simple comparison + this.normal = normal; + } else { + this.normal = null; + } + } + + /** + * Get the original name before normalization. + * + * @return the original name before normalization + */ + public String getName() { + return name; + } + + /** + * Get the normalized name. + * + * @return the normal name + */ + public String getNormal() { + return normal != null ? normal : name; + } + + /** + * Normalize a string according to the rules of this class. + * + * @param name an string + * @return the normalized string + */ + public static String normalize(String name) { + return Normalizer.normalize(name.toLowerCase(), Form.NFC); + } + + /** + * Create a new instance. + * + * @param name the name + * @return an instance + */ + public static Normal normal(String name) { + return new Normal(name); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Normal that = (Normal) o; + + return getNormal().equals(that.getNormal()); + + } + + @Override + public int hashCode() { + return getNormal().hashCode(); + } + + /** + * Return the un-normalized name. + * + * @return the un-normalized name + */ + @Override + public String toString() { + return name; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/SpongeUtil.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/SpongeUtil.java new file mode 100644 index 000000000..4836dc6fa --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/SpongeUtil.java @@ -0,0 +1,205 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.google.common.collect.Maps; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.config.WorldConfiguration; + +import java.util.Map; + +public final class SpongeUtil { + + private static Map> waterloggable = Maps.newHashMap(); + + private SpongeUtil() { + } + + private static boolean isReplacable(BlockType blockType) { + return blockType == BlockTypes.WATER || blockType == BlockTypes.SEAGRASS + || blockType == BlockTypes.TALL_SEAGRASS || blockType == BlockTypes.KELP_PLANT + || blockType == BlockTypes.KELP; + } + + /** + * Remove water around a sponge. + * + * @param world The world the sponge is in + * @param ox The x coordinate of the 'sponge' block + * @param oy The y coordinate of the 'sponge' block + * @param oz The z coordinate of the 'sponge' block + */ + public static void clearSpongeWater(World world, int ox, int oy, int oz) { + WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + + for (int cx = -wcfg.spongeRadius; cx <= wcfg.spongeRadius; cx++) { + for (int cy = -wcfg.spongeRadius; cy <= wcfg.spongeRadius; cy++) { + for (int cz = -wcfg.spongeRadius; cz <= wcfg.spongeRadius; cz++) { + BlockVector3 vector = BlockVector3.at(ox + cx, oy + cy, oz + cz); + BaseBlock block = world.getFullBlock(vector); + BlockType blockType = block.getBlockType(); + if (isReplacable(blockType)) { + try { + world.setBlock(vector, BlockTypes.AIR.getDefaultState()); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } else { + @SuppressWarnings("unchecked") + Property waterloggedProp = waterloggable.computeIfAbsent(blockType, + (bt -> (Property) bt.getPropertyMap().get("waterlogged"))); + if (waterloggedProp != null) { + try { + world.setBlock(vector, block.with(waterloggedProp, false)); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + } + } + } + + /** + * Sets the given block to fluid water. + * Used by addSpongeWater() + * + * @param world the world + * @param ox x + * @param oy y + * @param oz z + */ + private static void setBlockToWater(World world, int ox, int oy, int oz) throws WorldEditException { + BlockVector3 vector = BlockVector3.at(ox, oy, oz); + if (world.getBlock(vector).getBlockType().getMaterial().isAir()) { + world.setBlock(vector, BlockTypes.WATER.getDefaultState()); + } + } + + /** + * Add water around a sponge. + * + * @param world The world the sponge is located in + * @param ox The x coordinate of the 'sponge' block + * @param oy The y coordinate of the 'sponge' block + * @param oz The z coordinate of the 'sponge' block + */ + public static void addSpongeWater(World world, int ox, int oy, int oz) { + WorldConfiguration wcfg = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + + // The negative x edge + int cx = ox - wcfg.spongeRadius - 1; + for (int cy = oy - wcfg.spongeRadius - 1; cy <= oy + wcfg.spongeRadius + 1; cy++) { + for (int cz = oz - wcfg.spongeRadius - 1; cz <= oz + wcfg.spongeRadius + 1; cz++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx + 1, cy, cz); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + + // The positive x edge + cx = ox + wcfg.spongeRadius + 1; + for (int cy = oy - wcfg.spongeRadius - 1; cy <= oy + wcfg.spongeRadius + 1; cy++) { + for (int cz = oz - wcfg.spongeRadius - 1; cz <= oz + wcfg.spongeRadius + 1; cz++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx - 1, cy, cz); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + + // The negative y edge + int cy = oy - wcfg.spongeRadius - 1; + for (cx = ox - wcfg.spongeRadius - 1; cx <= ox + wcfg.spongeRadius + 1; cx++) { + for (int cz = oz - wcfg.spongeRadius - 1; cz <= oz + wcfg.spongeRadius + 1; cz++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx, cy + 1, cz); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + + // The positive y edge + cy = oy + wcfg.spongeRadius + 1; + for (cx = ox - wcfg.spongeRadius - 1; cx <= ox + wcfg.spongeRadius + 1; cx++) { + for (int cz = oz - wcfg.spongeRadius - 1; cz <= oz + wcfg.spongeRadius + 1; cz++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx, cy - 1, cz); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + + // The negative z edge + int cz = oz - wcfg.spongeRadius - 1; + for (cx = ox - wcfg.spongeRadius - 1; cx <= ox + wcfg.spongeRadius + 1; cx++) { + for (cy = oy - wcfg.spongeRadius - 1; cy <= oy + wcfg.spongeRadius + 1; cy++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx, cy, cz + 1); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + + // The positive z edge + cz = oz + wcfg.spongeRadius + 1; + for (cx = ox - wcfg.spongeRadius - 1; cx <= ox + wcfg.spongeRadius + 1; cx++) { + for (cy = oy - wcfg.spongeRadius - 1; cy <= oy + wcfg.spongeRadius + 1; cy++) { + BlockVector3 vector = BlockVector3.at(cx, cy, cz); + if (isReplacable(world.getBlock(vector).getBlockType())) { + try { + setBlockToWater(world, cx, cy, cz - 1); + } catch (WorldEditException e) { + e.printStackTrace(); + } + } + } + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/WorldGuardExceptionConverter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/WorldGuardExceptionConverter.java new file mode 100644 index 000000000..89492df2d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/WorldGuardExceptionConverter.java @@ -0,0 +1,98 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.sk89q.minecraft.util.commands.CommandException; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.internal.command.exception.ExceptionConverterHelper; +import com.sk89q.worldedit.internal.command.exception.ExceptionMatch; +import com.sk89q.worldedit.util.auth.AuthorizationException; +import com.sk89q.worldedit.util.formatting.component.InvalidComponentException; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.managers.storage.StorageException; +import com.sk89q.worldguard.protection.util.UnresolvedNamesException; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.RejectedExecutionException; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class WorldGuardExceptionConverter extends ExceptionConverterHelper { + + private static final Pattern numberFormat = Pattern.compile("^For input string: \"(.*)\"$"); + + private CommandException newCommandException(String message, Throwable cause) { + return new CommandException(message, cause); + } + + @ExceptionMatch + public void convert(NumberFormatException e) throws CommandException { + final Matcher matcher = numberFormat.matcher(e.getMessage()); + + if (matcher.matches()) { + throw newCommandException("Number expected; string \"" + matcher.group(1) + + "\" given.", e); + } else { + throw newCommandException("Number expected; string given.", e); + } + } + + @ExceptionMatch + public void convert(InvalidComponentException e) throws CommandException { + throw newCommandException(e.getMessage(), e); + } + + @ExceptionMatch + public void convert(StorageException e) throws CommandException { + WorldGuard.logger.log(Level.WARNING, "Error loading/saving regions", e); + throw newCommandException("Region data could not be loaded/saved: " + e.getMessage(), e); + } + + @ExceptionMatch + public void convert(RejectedExecutionException e) throws CommandException { + throw newCommandException("There are currently too many tasks queued to add yours. Use /wg running to list queued and running tasks.", e); + } + + @ExceptionMatch + public void convert(CancellationException e) throws CommandException { + throw newCommandException("Task was cancelled.", e); + } + + @ExceptionMatch + public void convert(InterruptedException e) throws CommandException { + throw newCommandException("Task was interrupted.", e); + } + + @ExceptionMatch + public void convert(WorldEditException e) throws CommandException { + throw newCommandException(e.getMessage(), e); + } + + @ExceptionMatch + public void convert(UnresolvedNamesException e) throws CommandException { + throw newCommandException(e.getMessage(), e); + } + + @ExceptionMatch + public void convert(AuthorizationException e) throws CommandException { + throw newCommandException("You don't have permission to do that.", e); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java new file mode 100644 index 000000000..41a58ec5b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/EntryBase.java @@ -0,0 +1,30 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.collect; + +public class EntryBase { + + protected long key; + + public EntryBase(long key) { + this.key = key; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java new file mode 100644 index 000000000..d5ef420a4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongBaseHashTable.java @@ -0,0 +1,130 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.collect; + +import java.util.ArrayList; +import java.util.Arrays; + +public class LongBaseHashTable extends LongHash { + + EntryBase[][][] values = new EntryBase[256][][]; + EntryBase cache = null; + + public void put(int msw, int lsw, EntryBase entry) { + put(entry); + } + + public EntryBase getEntry(int msw, int lsw) { + return getEntry(toLong(msw, lsw)); + } + + public synchronized void put(EntryBase entry) { + int mainIdx = (int) (entry.key & 255); + EntryBase[][] outer = this.values[mainIdx]; + if (outer == null) this.values[mainIdx] = outer = new EntryBase[256][]; + + int outerIdx = (int) ((entry.key >> 32) & 255); + EntryBase[] inner = outer[outerIdx]; + + if (inner == null) { + outer[outerIdx] = inner = new EntryBase[5]; + inner[0] = this.cache = entry; + } else { + int i; + for (i = 0; i < inner.length; i++) { + if (inner[i] == null || inner[i].key == entry.key) { + inner[i] = this.cache = entry; + return; + } + } + + outer[outerIdx] = inner = Arrays.copyOf(inner, i + i); + inner[i] = entry; + } + } + + public synchronized EntryBase getEntry(long key) { + return containsKey(key) ? cache : null; + } + + public synchronized boolean containsKey(long key) { + if (this.cache != null && cache.key == key) return true; + + int outerIdx = (int) ((key >> 32) & 255); + EntryBase[][] outer = this.values[(int) (key & 255)]; + if (outer == null) return false; + + EntryBase[] inner = outer[outerIdx]; + if (inner == null) return false; + + for (int i = 0; i < inner.length; i++) { + EntryBase e = inner[i]; + if (e == null) { + return false; + } else if (e.key == key) { + this.cache = e; + return true; + } + } + return false; + } + + public synchronized void remove(long key) { + EntryBase[][] outer = this.values[(int) (key & 255)]; + if (outer == null) return; + + EntryBase[] inner = outer[(int) ((key >> 32) & 255)]; + if (inner == null) return; + + for (int i = 0; i < inner.length; i++) { + if (inner[i] == null) continue; + + if (inner[i].key == key) { + for (i++; i < inner.length; i++) { + if (inner[i] == null) break; + inner[i - 1] = inner[i]; + } + + inner[i-1] = null; + this.cache = null; + return; + } + } + } + + public synchronized ArrayList entries() { + ArrayList ret = new ArrayList<>(); + + for (EntryBase[][] outer : this.values) { + if (outer == null) continue; + + for (EntryBase[] inner : outer) { + if (inner == null) continue; + + for (EntryBase entry : inner) { + if (entry == null) break; + + ret.add(entry); + } + } + } + return ret; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java new file mode 100644 index 000000000..89554007a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHash.java @@ -0,0 +1,48 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.collect; + +public abstract class LongHash { + + public static long toLong(int msw, int lsw) { + return ((long) msw << 32) + lsw - Integer.MIN_VALUE; + } + + public static int msw(long l) { + return (int) (l >> 32); + } + + public static int lsw(long l) { + return (int) (l & 0xFFFFFFFF) + Integer.MIN_VALUE; + } + + public boolean containsKey(int msw, int lsw) { + return containsKey(toLong(msw, lsw)); + } + + public void remove(int msw, int lsw) { + remove(toLong(msw, lsw)); + } + + public abstract boolean containsKey(long key); + + public abstract void remove(long key); + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java new file mode 100644 index 000000000..a23789d1d --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashSet.java @@ -0,0 +1,199 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.collect; + +import java.util.Arrays; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; + +public class LongHashSet extends LongHash { + + protected long[][][] values = new long[256][][]; + protected int count = 0; + protected ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + protected ReadLock rl = rwl.readLock(); + protected WriteLock wl = rwl.writeLock(); + + public boolean isEmpty() { + rl.lock(); + try { + return this.count == 0; + } finally { + rl.unlock(); + } + } + + public int size() { + return count; + } + + public void add(int msw, int lsw) { + add(toLong(msw, lsw)); + } + + public void add(long key) { + wl.lock(); + try { + int mainIdx = (int) (key & 255); + long outer[][] = this.values[mainIdx]; + if (outer == null) this.values[mainIdx] = outer = new long[256][]; + + int outerIdx = (int) ((key >> 32) & 255); + long inner[] = outer[outerIdx]; + + if (inner == null) { + synchronized (this) { + outer[outerIdx] = inner = new long[1]; + inner[0] = key; + this.count++; + } + } else { + int i; + for (i = 0; i < inner.length; i++) { + if (inner[i] == key) { + return; + } + } + inner = Arrays.copyOf(inner, i + 1); + outer[outerIdx] = inner; + inner[i] = key; + this.count++; + } + } finally { + wl.unlock(); + } + } + + public boolean containsKey(long key) { + rl.lock(); + try { + long[][] outer = this.values[(int) (key & 255)]; + if (outer == null) return false; + + long[] inner = outer[(int) ((key >> 32) & 255)]; + if (inner == null) return false; + + for (long entry : inner) { + if (entry == key) return true; + } + return false; + } finally { + rl.unlock(); + } + } + + public void remove(long key) { + wl.lock(); + try { + long[][] outer = this.values[(int) (key & 255)]; + if (outer == null) return; + + long[] inner = outer[(int) ((key >> 32) & 255)]; + if (inner == null) return; + + int max = inner.length - 1; + for (int i = 0; i <= max; i++) { + if (inner[i] == key) { + this.count--; + if (i != max) { + inner[i] = inner[max]; + } + + outer[(int) ((key >> 32) & 255)] = (max == 0 ? null : Arrays.copyOf(inner, max)); + return; + } + } + } finally { + wl.unlock(); + } + } + + public long popFirst() { + wl.lock(); + try { + for (long[][] outer: this.values) { + if (outer == null) continue; + + for (int i = 0; i < outer.length; i++) { + long[] inner = outer[i]; + if (inner == null || inner.length == 0) continue; + + this.count--; + long ret = inner[inner.length - 1]; + outer[i] = Arrays.copyOf(inner, inner.length - 1); + + return ret; + } + } + } finally { + wl.unlock(); + } + return 0; + } + + public long[] popAll() { + int index = 0; + wl.lock(); + try { + long[] ret = new long[this.count]; + for (long[][] outer : this.values) { + if (outer == null) continue; + + for (int oIdx = outer.length - 1; oIdx >= 0; oIdx--) { + long[] inner = outer[oIdx]; + if (inner == null) continue; + + for (long entry: inner) { + ret[index++] = entry; + } + outer[oIdx] = null; + } + } + count = 0; + return ret; + } finally { + wl.unlock(); + } + } + + public long[] keys() { + int index = 0; + rl.lock(); + try { + long[] ret = new long[this.count]; + for (long[][] outer : this.values) { + if (outer == null) continue; + + for (long[] inner : outer) { + if (inner == null) continue; + + for (long entry : inner) { + ret[index++] = entry; + } + } + } + return ret; + } finally { + rl.unlock(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java new file mode 100644 index 000000000..0a6afc350 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/collect/LongHashTable.java @@ -0,0 +1,65 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.collect; + +import java.util.ArrayList; + +public class LongHashTable extends LongBaseHashTable { + + public void put(int msw, int lsw, V value) { + put(toLong(msw, lsw), value); + } + + public V get(int msw, int lsw) { + return get(toLong(msw, lsw)); + } + + public synchronized void put(long key, V value) { + put(new Entry(key, value)); + } + + @SuppressWarnings("unchecked") + public synchronized V get(long key) { + Entry entry = ((Entry) getEntry(key)); + return entry != null ? entry.value : null; + } + + @SuppressWarnings("unchecked") + public synchronized ArrayList values() { + ArrayList ret = new ArrayList<>(); + + ArrayList entries = entries(); + + for (EntryBase entry : entries) { + ret.add(((Entry) entry).value); + } + return ret; + } + + private class Entry extends EntryBase { + V value; + + Entry(long k, V v) { + super(k); + this.value = v; + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/command/CommandFilter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/command/CommandFilter.java new file mode 100644 index 000000000..44dbfd6f5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/command/CommandFilter.java @@ -0,0 +1,207 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.command; + +import com.google.common.base.Predicate; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Checks whether a command is permitted with support for subcommands + * split by {@code \s} (regular expressions). + * + *

{@code permitted} always overrides {@code denied} (unlike other parts of + * WorldGuard). Either can be null. If both are null, then every command is + * permitted. If only {@code permitted} is null, then all commands but + * those in the list of denied are permitted. If only {@code denied} is null, + * then only commands in the list of permitted are permitted. If neither are + * null, only permitted commands are permitted and the list of denied commands + * is not used.

+ * + *

The test is case in-sensitive.

+ */ +public class CommandFilter implements Predicate { + + @Nullable + private final Collection permitted; + @Nullable + private final Collection denied; + + /** + * Create a new instance. + * + * @param permitted a list of rules for permitted commands + * @param denied a list of rules for denied commands + */ + public CommandFilter(@Nullable Collection permitted, @Nullable Collection denied) { + this.permitted = permitted; + this.denied = denied; + } + + @SuppressWarnings("StatementWithEmptyBody") + @Override + public boolean apply(String command) { + command = command.toLowerCase().replaceAll("^/([^ :]*:)?", "/"); + + /* + * denied used allow? + * x x no + * x x y no + * x y x yes + * x y x y no + * + * permitted used allow? + * x x yes + * x x y yes + * x y x no + * x y x y yes + */ + String result = ""; + String[] usedParts = command.split("\\s+"); + if (denied != null) { + denied: + for (String deniedCommand : denied) { + String[] deniedParts = deniedCommand.split("\\s+"); + for (int i = 0; i < deniedParts.length && i < usedParts.length; i++) { + if (deniedParts[i].equalsIgnoreCase(usedParts[i])) { + // first part matches - check if it's the whole thing + if (i + 1 == deniedParts.length) { + // consumed all denied parts, block entire command + result = deniedCommand; + break denied; + } else { + // more denied parts to check, also check used length + if (i + 1 == usedParts.length) { + // all that was used, but there is more in denied + // allow this, check next command in flag + continue denied; + } else { + // more in both denied and used, continue checking + } + } + } else { + // found non-matching part, stop checking this command + continue denied; + } + } + } + } + + if (permitted != null) { + permitted: + for (String permittedCommand : permitted) { + String[] permittedParts = permittedCommand.split("\\s+"); + for (int i = 0; i < permittedParts.length && i < usedParts.length; i++) { + if (permittedParts[i].equalsIgnoreCase(usedParts[i])) { + // this part matches - check if it's the whole thing + if (i + 1 == permittedParts.length) { + // consumed all permitted parts before reaching used length + // this command is definitely permitted + result = ""; + break permitted; + } else { + // more permitted parts to check + if (i + 1 == usedParts.length) { + // all that was used, but there is more in permitted + // block for now, check next part of flag + result = command; + continue permitted; + } else { + // more in both permitted and used, continue checking for match + } + } + } else { + // doesn't match at all, block it, check next flag string + result = command; + continue permitted; + } + } + } + } + + return result.isEmpty(); + } + + /** + * Builder class for {@code CommandFilter}. + * + *

If {@link #permit(String...)} is never called, then the + * permitted rule list will be {@code null}. Likewise if + * {@link #deny(String...)} is never called.

+ */ + public static class Builder { + private Set permitted; + private Set denied; + + /** + * Create a new instance. + */ + public Builder() { + } + + /** + * Permit the given list of commands. + * + * @param rules list of commands + * @return the builder object + */ + public Builder permit(String ... rules) { + checkNotNull(rules); + if (permitted == null) { + permitted = new HashSet<>(); + } + permitted.addAll(Arrays.asList(rules)); + return this; + } + + /** + * Deny the given list of commands. + * + * @param rules list of commands + * @return the builder object + */ + public Builder deny(String ... rules) { + checkNotNull(rules); + if (denied == null) { + denied = new HashSet<>(); + } + denied.addAll(Arrays.asList(rules)); + return this; + } + + /** + * Create a command filter. + * + * @return a new command filter + */ + public CommandFilter build() { + return new CommandFilter( + permitted != null ? new HashSet<>(permitted) : null, + denied != null ? new HashSet<>(denied) : null); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/concurrent/EvenMoreExecutors.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/concurrent/EvenMoreExecutors.java new file mode 100644 index 000000000..96ccf95ff --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/concurrent/EvenMoreExecutors.java @@ -0,0 +1,73 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.concurrent; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Provides additional executors. + */ +public final class EvenMoreExecutors { + + private EvenMoreExecutors() { + } + + /** + * Creates a thread pool that creates new threads as needed up to + * a maximum number of threads, but will reuse previously constructed + * threads when they are available. + * + * @param minThreads the minimum number of threads to have at a given time + * @param maxThreads the maximum number of threads to have at a given time + * @param queueSize the size of the queue before new submissions are rejected + * @return the newly created thread pool + */ + public static ExecutorService newBoundedCachedThreadPool(int minThreads, int maxThreads, int queueSize) { + return newBoundedCachedThreadPool(minThreads, maxThreads, queueSize, null);} + + /** + * Creates a thread pool that creates new threads as needed up to + * a maximum number of threads, but will reuse previously constructed + * threads when they are available. + * + * @param minThreads the minimum number of threads to have at a given time + * @param maxThreads the maximum number of threads to have at a given time + * @param queueSize the size of the queue before new submissions are rejected + * @param threadFormat thread name formatter + * @return the newly created thread pool + */ + public static ExecutorService newBoundedCachedThreadPool(int minThreads, int maxThreads, int queueSize, String threadFormat) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + minThreads, maxThreads, + 60L, TimeUnit.SECONDS, + new LinkedBlockingDeque<>(queueSize)); + threadPoolExecutor.allowCoreThreadTimeOut(true); + if (threadFormat != null) { + threadPoolExecutor.setThreadFactory(new ThreadFactoryBuilder().setNameFormat(threadFormat).build()); + } + return threadPoolExecutor; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/BlacklistNotify.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/BlacklistNotify.java new file mode 100644 index 000000000..b4edbea7b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/BlacklistNotify.java @@ -0,0 +1,32 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.formatting.component; + +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; +import com.sk89q.worldguard.blacklist.event.BlacklistEvent; + +public class BlacklistNotify extends Notify { + + public BlacklistNotify(BlacklistEvent event, String comment) { + super(event.getCauseName(), " (" + event.getDescription() + ") "); + append(TextComponent.of(event.getTarget().getFriendlyName() + (comment != null ? " (" + comment + ")" : "") + ".", TextColor.WHITE)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/Notify.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/Notify.java new file mode 100644 index 000000000..9b9cdf4a9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/formatting/component/Notify.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.formatting.component; + +import com.sk89q.worldedit.util.formatting.component.TextComponentProducer; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; + +public class Notify extends TextComponentProducer { + + public Notify(String cause, String description) { + append(TextComponent.of("WG: ", TextColor.GRAY)); + append(TextComponent.of(cause, TextColor.LIGHT_PURPLE)); + append(TextComponent.of(description, TextColor.GOLD)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/io/Closer.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/io/Closer.java new file mode 100644 index 000000000..fe56d56fd --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/io/Closer.java @@ -0,0 +1,308 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.io; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public final class Closer implements Closeable { + + private static final Logger logger = Logger.getLogger(Closer.class.getCanonicalName()); + + /** + * The suppressor implementation to use for the current Java version. + */ + private static final Suppressor SUPPRESSOR = SuppressingSuppressor.isAvailable() + ? SuppressingSuppressor.INSTANCE + : LoggingSuppressor.INSTANCE; + + /** + * Creates a new {@link Closer}. + */ + public static Closer create() { + return new Closer(SUPPRESSOR); + } + + @VisibleForTesting + final Suppressor suppressor; + + // only need space for 2 elements in most cases, so try to use the smallest array possible + private final Deque stack = new ArrayDeque<>(4); + private Throwable thrown; + + @VisibleForTesting + Closer(Suppressor suppressor) { + this.suppressor = checkNotNull(suppressor); // checkNotNull to satisfy null tests + } + + /** + * Registers the given {@code closeable} to be closed when this {@code Closer} is + * {@linkplain #close closed}. + * + * @return the given {@code closeable} + */ + // close. this word no longer has any meaning to me. + public C register(C closeable) { + stack.push(closeable); + return closeable; + } + + /** + * Registers the given {@code connection} to be closed when this + * {@code Closer} is {@linkplain #close closed}. + * + * @return the given {@code connection} + */ + public C register(final C connection) { + register(new Closeable() { + @Override + public void close() throws IOException { + try { + connection.close(); + } catch (SQLException e) { + throw new IOException("Failed to close", e); + } + } + }); + return connection; + } + + /** + * Registers the given {@code statement} to be closed when this + * {@code Closer} is {@linkplain #close closed}. + * + * @return the given {@code statement} + */ + public C register(final C statement) { + register(new Closeable() { + @Override + public void close() throws IOException { + try { + statement.close(); + } catch (SQLException e) { + throw new IOException("Failed to close", e); + } + } + }); + return statement; + } + + /** + * Registers the given {@code resultSet} to be closed when this + * {@code Closer} is {@linkplain #close closed}. + * + * @return the given {@code resultSet} + */ + public C register(final C resultSet) { + register(new Closeable() { + @Override + public void close() throws IOException { + try { + resultSet.close(); + } catch (SQLException e) { + throw new IOException("Failed to close", e); + } + } + }); + return resultSet; + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an + * {@code IOException}, {@code RuntimeException} or {@code Error}. Otherwise, it will be rethrown + * wrapped in a {@code RuntimeException}. Note: Be sure to declare all of the checked + * exception types your try block can throw when calling an overload of this method so as to avoid + * losing the original exception type. + * + *

This method always throws, and as such should be called as + * {@code throw closer.rethrow(e);} to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + */ + public RuntimeException rethrow(Throwable e) throws IOException { + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + throw Throwables.propagate(e); + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an + * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of the + * given type. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. Note: + * Be sure to declare all of the checked exception types your try block can throw when calling an + * overload of this method so as to avoid losing the original exception type. + * + *

This method always throws, and as such should be called as + * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + * @throws X when the given throwable is of the declared type X + */ + public RuntimeException rethrow(Throwable e, + Class declaredType) throws IOException, X { + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + Throwables.propagateIfPossible(e, declaredType); + throw Throwables.propagate(e); + } + + /** + * Stores the given throwable and rethrows it. It will be rethrown as is if it is an + * {@code IOException}, {@code RuntimeException}, {@code Error} or a checked exception of either + * of the given types. Otherwise, it will be rethrown wrapped in a {@code RuntimeException}. + * Note: Be sure to declare all of the checked exception types your try block can throw + * when calling an overload of this method so as to avoid losing the original exception type. + * + *

This method always throws, and as such should be called as + * {@code throw closer.rethrow(e, ...);} to ensure the compiler knows that it will throw. + * + * @return this method does not return; it always throws + * @throws IOException when the given throwable is an IOException + * @throws X1 when the given throwable is of the declared type X1 + * @throws X2 when the given throwable is of the declared type X2 + */ + public RuntimeException rethrow( + Throwable e, Class declaredType1, Class declaredType2) throws IOException, X1, X2 { + thrown = e; + Throwables.propagateIfPossible(e, IOException.class); + Throwables.propagateIfPossible(e, declaredType1, declaredType2); + throw Throwables.propagate(e); + } + + /** + * Closes all {@code Closeable} instances that have been added to this {@code Closer}. If an + * exception was thrown in the try block and passed to one of the {@code exceptionThrown} methods, + * any exceptions thrown when attempting to close a closeable will be suppressed. Otherwise, the + * first exception to be thrown from an attempt to close a closeable will be thrown and any + * additional exceptions that are thrown after that will be suppressed. + */ + @Override + public void close() throws IOException { + Throwable throwable = thrown; + + // close closeables in LIFO order + while (!stack.isEmpty()) { + Closeable closeable = stack.pop(); + try { + closeable.close(); + } catch (Throwable e) { + if (throwable == null) { + throwable = e; + } else { + suppressor.suppress(closeable, throwable, e); + } + } + } + + if (thrown == null && throwable != null) { + Throwables.propagateIfPossible(throwable, IOException.class); + throw new AssertionError(throwable); // not possible + } + } + + /** + * Close quietly. + */ + public void closeQuietly() { + try { + close(); + } catch (IOException ignored) { + } + } + + /** + * Suppression strategy interface. + */ + @VisibleForTesting interface Suppressor { + /** + * Suppresses the given exception ({@code suppressed}) which was thrown when attempting to close + * the given closeable. {@code thrown} is the exception that is actually being thrown from the + * method. Implementations of this method should not throw under any circumstances. + */ + void suppress(Closeable closeable, Throwable thrown, Throwable suppressed); + } + + /** + * Suppresses exceptions by logging them. + */ + @VisibleForTesting static final class LoggingSuppressor implements Suppressor { + + static final LoggingSuppressor INSTANCE = new LoggingSuppressor(); + + @Override + public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { + // log to the same place as Closeables + logger.log(Level.WARNING, "Suppressing exception thrown when closing " + closeable, suppressed); + } + } + + /** + * Suppresses exceptions by adding them to the exception that will be thrown using JDK7's + * addSuppressed(Throwable) mechanism. + */ + @VisibleForTesting static final class SuppressingSuppressor implements Suppressor { + + static final SuppressingSuppressor INSTANCE = new SuppressingSuppressor(); + + static boolean isAvailable() { + return addSuppressed != null; + } + + static final Method addSuppressed = getAddSuppressed(); + + private static Method getAddSuppressed() { + try { + return Throwable.class.getMethod("addSuppressed", Throwable.class); + } catch (Throwable e) { + return null; + } + } + + @Override + public void suppress(Closeable closeable, Throwable thrown, Throwable suppressed) { + // ensure no exceptions from addSuppressed + if (thrown == suppressed) { + return; + } + try { + addSuppressed.invoke(thrown, suppressed); + } catch (Throwable e) { + // if, somehow, IllegalAccessException or another exception is thrown, fall back to logging + LoggingSuppressor.INSTANCE.suppress(closeable, thrown, suppressed); + } + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/LoggerToChatHandler.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/LoggerToChatHandler.java new file mode 100644 index 000000000..0eeef0e09 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/LoggerToChatHandler.java @@ -0,0 +1,77 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.logging; + +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.util.formatting.component.SubtleFormat; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; + +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +/** + * Sends all logger messages to a player. + */ +public class LoggerToChatHandler extends Handler { + private final Formatter formatter = new Formatter() { + @Override + public String format(LogRecord record) { + return formatMessage(record); + } + }; + + /** + * Player. + */ + private Actor player; + + /** + * Construct the object. + * + * @param player + */ + public LoggerToChatHandler(Actor player) { + this.player = player; + } + + /** + * Close the handler. + */ + @Override + public void close() { + } + + /** + * Flush the output. + */ + @Override + public void flush() { + } + + /** + * Publish a log record. + */ + @Override + public void publish(LogRecord record) { + player.print(SubtleFormat.wrap(record.getLevel().getName() + ": ").append(TextComponent.of(formatter.format(record), TextColor.WHITE))); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/RecordMessagePrefixer.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/RecordMessagePrefixer.java new file mode 100644 index 000000000..f18cbde85 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/logging/RecordMessagePrefixer.java @@ -0,0 +1,77 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.logging; + +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class RecordMessagePrefixer extends Handler { + + private final Logger parentLogger; + private final String prefix; + + public RecordMessagePrefixer(Logger parentLogger, String prefix) { + checkNotNull(parentLogger); + checkNotNull(prefix); + + this.parentLogger = parentLogger; + this.prefix = prefix; + } + + @Override + public void publish(LogRecord record) { + // Ideally we would make a copy of the record + record.setMessage(prefix + record.getMessage()); + parentLogger.log(record); + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + + /** + * Register a prefix handler on the given logger. + * + * @param logger the logger + * @param prefix the prefix + */ + public static void register(Logger logger, String prefix) { + checkNotNull(logger); + + // Fix issues with multiple classloaders loading the same class + String className = RecordMessagePrefixer.class.getCanonicalName(); + + logger.setUseParentHandlers(false); + for (Handler handler : logger.getHandlers()) { + if (handler.getClass().getCanonicalName().equals(className)) { + logger.removeHandler(handler); + } + } + logger.addHandler(new RecordMessagePrefixer(logger.getParent(), prefix)); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/SamplerBuilder.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/SamplerBuilder.java new file mode 100644 index 000000000..2d834155a --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/SamplerBuilder.java @@ -0,0 +1,157 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profiler; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.Map; +import java.util.SortedMap; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; +import java.util.concurrent.CancellationException; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +public class SamplerBuilder { + + private static final Timer timer = new Timer("WorldGuard Sampler", true); + private int interval = 100; + private long runTime = TimeUnit.MINUTES.toMillis(5); + private Predicate threadFilter = thread -> true; + + public int getInterval() { + return interval; + } + + public void setInterval(int interval) { + checkArgument(interval >= 1, "interval >= 1"); + this.interval = interval; + } + + public Predicate getThreadFilter() { + return threadFilter; + } + + public void setThreadFilter(Predicate threadFilter) { + checkNotNull(threadFilter, "threadFilter"); + this.threadFilter = threadFilter; + } + + public long getRunTime(TimeUnit timeUnit) { + return timeUnit.convert(runTime, TimeUnit.MILLISECONDS); + } + + public void setRunTime(long time, TimeUnit timeUnit) { + checkArgument(time > 0, "time > 0"); + this.runTime = timeUnit.toMillis(time); + } + + public Sampler start() { + Sampler sampler = new Sampler(interval, threadFilter, System.currentTimeMillis() + runTime); + timer.scheduleAtFixedRate(sampler, 0, interval); + return sampler; + } + + public static class Sampler extends TimerTask { + private final int interval; + private final Predicate threadFilter; + private final long endTime; + + private final SortedMap nodes = new TreeMap<>(); + private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + private final SettableFuture future = SettableFuture.create(); + + private Sampler(int interval, Predicate threadFilter, long endTime) { + this.interval = interval; + this.threadFilter = threadFilter; + this.endTime = endTime; + } + + public ListenableFuture getFuture() { + return future; + } + + private Map getData() { + return nodes; + } + + private StackNode getNode(String name) { + StackNode node = nodes.get(name); + if (node == null) { + node = new StackNode(name); + nodes.put(name, node); + } + return node; + } + + @Override + public boolean cancel() { + future.setException(new CancellationException()); + return super.cancel(); + } + + @Override + public synchronized void run() { + try { + if (endTime <= System.currentTimeMillis()) { + future.set(this); + cancel(); + return; + } + + ThreadInfo[] threadDumps = threadBean.dumpAllThreads(false, false); + for (ThreadInfo threadInfo : threadDumps) { + String threadName = threadInfo.getThreadName(); + StackTraceElement[] stack = threadInfo.getStackTrace(); + + if (threadName != null && stack != null && threadFilter.test(threadInfo)) { + StackNode node = getNode(threadName); + node.log(stack, interval); + } + } + } catch (Throwable t) { + future.setException(t); + super.cancel(); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : getData().entrySet()) { + builder.append(entry.getKey()); + builder.append(" "); + builder.append(entry.getValue().getTotalTime()).append("ms"); + builder.append("\n"); + entry.getValue().writeString(builder, 1); + } + return builder.toString(); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackNode.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackNode.java new file mode 100644 index 000000000..6aa2a43ea --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackNode.java @@ -0,0 +1,121 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profiler; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class StackNode implements Comparable { + + private final String name; + private final Map children = Maps.newHashMap(); + private long totalTime; + + public StackNode(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Collection getChildren() { + List list = Lists.newArrayList(children.values()); + Collections.sort(list); + return list; + } + + public StackNode getChild(String name) { + StackNode child = children.get(name); + if (child == null) { + child = new StackNode(name); + children.put(name, child); + } + return child; + } + + public StackNode getChild(String className, String methodName) { + StackTraceNode node = new StackTraceNode(className, methodName); + StackNode child = children.get(node.getName()); + if (child == null) { + child = node; + children.put(node.getName(), node); + } + return child; + } + + public long getTotalTime() { + return totalTime; + } + + public void log(long time) { + totalTime += time; + } + + private void log(StackTraceElement[] elements, int skip, long time) { + log(time); + + if (elements.length - skip == 0) { + return; + } + + StackTraceElement bottom = elements[elements.length - (skip + 1)]; + getChild(bottom.getClassName(), bottom.getMethodName()) + .log(elements, skip + 1, time); + } + + public void log(StackTraceElement[] elements, long time) { + log(elements, 0, time); + } + + @Override + public int compareTo(StackNode o) { + return getName().compareTo(o.getName()); + } + + void writeString(StringBuilder builder, int indent) { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < indent; i++) { + b.append(" "); + } + String padding = b.toString(); + + for (StackNode child : getChildren()) { + builder.append(padding).append(child.getName()); + builder.append(" "); + builder.append(child.getTotalTime()).append("ms"); + builder.append("\n"); + child.writeString(builder, indent + 1); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + writeString(builder, 0); + return builder.toString(); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackTraceNode.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackTraceNode.java new file mode 100644 index 000000000..2f5c94fb4 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/StackTraceNode.java @@ -0,0 +1,46 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profiler; + +public class StackTraceNode extends StackNode { + + private final String className; + private final String methodName; + + public StackTraceNode(String className, String methodName) { + super(className + "." + methodName + "()"); + this.className = className; + this.methodName = methodName; + } + + public String getClassName() { + return className; + } + + public String getMethodName() { + return methodName; + } + + @Override + public int compareTo(StackNode o) { + return Long.compare(o.getTotalTime(), getTotalTime()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadIdFilter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadIdFilter.java new file mode 100644 index 000000000..2daaf6416 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadIdFilter.java @@ -0,0 +1,37 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profiler; + +import java.lang.management.ThreadInfo; +import java.util.function.Predicate; + +public class ThreadIdFilter implements Predicate { + + private final long id; + + public ThreadIdFilter(long id) { + this.id = id; + } + + @Override + public boolean test(ThreadInfo threadInfo) { + return threadInfo.getThreadId() == id; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadNameFilter.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadNameFilter.java new file mode 100644 index 000000000..4054af5e5 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/profiler/ThreadNameFilter.java @@ -0,0 +1,41 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.profiler; + +import java.lang.management.ThreadInfo; +import java.util.function.Predicate; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class ThreadNameFilter implements Predicate { + + private final String name; + + public ThreadNameFilter(String name) { + checkNotNull(name, "name"); + this.name = name; + } + + @Override + public boolean test(ThreadInfo threadInfo) { + return threadInfo.getThreadName().equalsIgnoreCase(name); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ApplicableRegionsReport.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ApplicableRegionsReport.java new file mode 100644 index 000000000..2ad34abbb --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ApplicableRegionsReport.java @@ -0,0 +1,54 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.report; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.report.DataReport; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.ApplicableRegionSet; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +public class ApplicableRegionsReport extends DataReport { + + public ApplicableRegionsReport(LocalPlayer player) { + super("Applicable regions"); + BlockVector3 position = player.getBlockIn().toVector().toBlockPoint(); + append("Location", player.getWorld().getName() + " @ " + position); + RegionManager mgr = WorldGuard.getInstance().getPlatform().getRegionContainer().get(player.getWorld()); + if (mgr == null) { + append("Regions", "Disabled for current world"); + } else { + ApplicableRegionSet rgs = mgr.getApplicableRegions(position); + if (rgs.getRegions().isEmpty()) { + append("Regions", "None"); + } else { + DataReport regions = new DataReport("Regions"); + for (ProtectedRegion region : rgs.getRegions()) { + boolean inherited = !region.contains(position); + regions.append(region.getId() + (inherited ? "*" : ""), new RegionReport(region)); + } + append(regions.getTitle(), regions); + } + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ConfigReport.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ConfigReport.java new file mode 100644 index 000000000..d1fefa191 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/ConfigReport.java @@ -0,0 +1,81 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.report; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.util.report.DataReport; +import com.sk89q.worldedit.util.report.HierarchyObjectReport; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.blacklist.Blacklist; +import com.sk89q.worldguard.config.WorldConfiguration; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +import java.util.List; + +public class ConfigReport extends DataReport { + + public ConfigReport() { + super("WorldGuard Configuration"); + + List worlds = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS).getWorlds(); + + append("Configuration", new HierarchyObjectReport("Configuration", WorldGuard.getInstance().getPlatform().getGlobalStateManager())); + + for (World world : worlds) { + WorldConfiguration config = WorldGuard.getInstance().getPlatform().getGlobalStateManager().get(world); + + DataReport report = new DataReport("World: " + world.getName()); + report.append("Configuration", new HierarchyObjectReport("Configuration", config)); + + Blacklist blacklist = config.getBlacklist(); + if (blacklist != null) { + DataReport section = new DataReport("Blacklist"); + section.append("Rule Count", blacklist.getItemCount()); + section.append("Whitelist Mode?", blacklist.isWhitelist()); + report.append(section.getTitle(), section); + } else { + report.append("Blacklist", ""); + } + + RegionManager regions = WorldGuard.getInstance().getPlatform().getRegionContainer().get(world); + if (regions != null) { + DataReport section = new DataReport("Regions"); + section.append("Region Count", regions.size()); + + ProtectedRegion global = regions.getRegion("__global__"); + if (global != null) { + section.append("__global__", new RegionReport(global)); + } else { + section.append("__global__", ""); + } + + report.append(section.getTitle(), section); + } else { + report.append("Regions", ""); + } + + append(report.getTitle(), report); + } + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/RegionReport.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/RegionReport.java new file mode 100644 index 000000000..95b91ba57 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/report/RegionReport.java @@ -0,0 +1,42 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.report; + +import com.sk89q.worldedit.util.report.DataReport; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; + +/** + * Reports on a region. + */ +public class RegionReport extends DataReport { + + public RegionReport(ProtectedRegion region) { + super("Region: " + region.getId()); + + append("Type", region.getType()); + append("Priority", region.getPriority()); + append("Parent", region.getParent() == null ? "" : region.getParent().getId()); + append("Owners", region.getOwners()); + append("Members", region.getMembers()); + append("Flags", region.getFlags()); + append("Bounds", region.getMinimumPoint() + " -> " + region.getMaximumPoint()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java new file mode 100644 index 000000000..cf72b19e9 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/main/java/com/sk89q/worldguard/util/sql/DataSourceConfig.java @@ -0,0 +1,144 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util.sql; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Describes a data source. + */ +public class DataSourceConfig { + + private final String dsn; + private final String username; + private final String password; + private final String tablePrefix; + + /** + * Create a new instance. + * + * @param dsn the DSN + * @param username the username + * @param password the password + * @param tablePrefix the table prefix + */ + public DataSourceConfig(String dsn, String username, String password, String tablePrefix) { + checkNotNull(dsn); + checkNotNull(username); + checkNotNull(password); + checkNotNull(tablePrefix); + + this.dsn = dsn; + this.username = username; + this.password = password; + this.tablePrefix = tablePrefix; + } + + /** + * Get the DSN. + * + * @return the DSN + */ + public String getDsn() { + return dsn; + } + + /** + * Get the username. + * + * @return the username + */ + public String getUsername() { + return username; + } + + /** + * Get the password. + * + * @return the password + */ + public String getPassword() { + return password; + } + + /** + * Get the table prefix. + * + * @return the table prefix + */ + public String getTablePrefix() { + return tablePrefix; + } + + /** + * Create a new instance with a new DSN. + * + * @param dsn a new DSN string + * @return a new instance + */ + public DataSourceConfig setDsn(String dsn) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new username. + * + * @param username a new username + * @return a new instance + */ + public DataSourceConfig setUsername(String username) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new password. + * + * @param password a new password + * @return a new instance + */ + public DataSourceConfig setPassword(String password) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new instance with a new table prefix. + * + * @param tablePrefix the new table prefix + * @return a new instance + */ + public DataSourceConfig setTablePrefix(String tablePrefix) { + return new DataSourceConfig(dsn, username, password, tablePrefix); + } + + /** + * Create a new connection. + * + * @return the new connection + * @throws SQLException raised if the connection cannot be instantiated + */ + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(dsn, username, password); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java new file mode 100644 index 000000000..e5bb902a1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/TestPlayer.java @@ -0,0 +1,283 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard; + +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.HandSide; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.weather.WeatherType; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.UUID; + +import javax.annotation.Nullable; + + +public class TestPlayer extends AbstractPlayerActor implements LocalPlayer { + + private final UUID uuid = UUID.randomUUID(); + private final String name; + private final Set groups = new HashSet<>(); + + public TestPlayer(String name) { + this.name = name; + } + + public void addGroup(String group) { + groups.add(group.toLowerCase()); + } + + @Override + public String getName() { + return name; + } + + @Override + public UUID getUniqueId() { + return uuid; + } + + @Override + public boolean hasGroup(String group) { + return groups.contains(group.toLowerCase()); + } + + @Override + public void kick(String msg) { + System.out.println("TestPlayer{" + this.name + "} kicked!"); + } + + @Override + public void ban(String msg) { + System.out.println("TestPlayer{" + this.name + "} banned!"); + } + + @Override + public double getHealth() { + return 0; + } + + @Override + public void setHealth(double health) { + + } + + @Override + public double getMaxHealth() { + return 0; + } + + @Override + public double getFoodLevel() { + return 0; + } + + @Override + public void setFoodLevel(double foodLevel) { + + } + + @Override + public double getSaturation() { + return 0; + } + + @Override + public void setSaturation(double saturation) { + + } + + @Override + public float getExhaustion() { + return 0; + } + + @Override + public void setExhaustion(float exhaustion) { + + } + + @Override + public WeatherType getPlayerWeather() { + return null; + } + + @Override + public void setPlayerWeather(WeatherType weather) { + + } + + @Override + public void resetPlayerWeather() { + + } + + @Override + public boolean isPlayerTimeRelative() { + return false; + } + + @Override + public long getPlayerTimeOffset() { + return 0; + } + + @Override + public void setPlayerTime(long time, boolean relative) { + + } + + @Override + public void resetPlayerTime() { + + } + + @Override + public int getFireTicks() { + return 0; + } + + @Override + public void setFireTicks(int fireTicks) { + + } + + @Override + public void setCompassTarget(Location location) { + + } + + @Override + public void sendTitle(String title, String subtitle) { + + } + + @Override + public void resetFallDistance() { + + } + + @Override + public void teleport(Location location, String successMessage, String failMessage) { + + } + + @Override + public void printRaw(String msg) { + System.out.println("-> TestPlayer{" + this.name + "}: " + msg); + } + + @Override + public void printDebug(String msg) { + + } + + @Override + public void print(String msg) { + + } + + @Override + public void printError(String msg) { + + } + + @Override + public void print(Component component) { + + } + + @Override + public Locale getLocale() { + return Locale.ENGLISH; + } + + @Override + public String[] getGroups() { + return groups.toArray(new String[groups.size()]); + } + + @Override + public boolean hasPermission(String perm) { + return true; + } + + @Override + public World getWorld() { + return null; + } + + @Override + public BaseItemStack getItemInHand(HandSide handSide) { + return null; + } + + @Override + public void giveItem(BaseItemStack itemStack) { + + } + + @Override + public BlockBag getInventoryBlockBag() { + return null; + } + + @Override + @Deprecated + public void setPosition(Vector3 pos, float pitch, float yaw) { + + } + + @Nullable + @Override + public BaseEntity getState() { + return null; + } + + @Override + public Location getLocation() { + return null; + } + + @Override + public boolean setLocation(Location location) { + return false; + } + + @Override + public SessionKey getSessionKey() { + return null; + } + + @Nullable + @Override + public T getFacet(Class cls) { + return null; + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/domains/DefaultDomainTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/domains/DefaultDomainTest.java new file mode 100644 index 000000000..86586657f --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/domains/DefaultDomainTest.java @@ -0,0 +1,116 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.domains; + +import com.sk89q.worldguard.TestPlayer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DefaultDomainTest { + + @Test + public void testContains() throws Exception { + TestPlayer player1 = new TestPlayer("test1"); + TestPlayer player2 = new TestPlayer("test2"); + player2.addGroup("group1"); + player2.addGroup("group2"); + TestPlayer player3 = new TestPlayer("test3"); + player3.addGroup("group1"); + player3.addGroup("group3"); + + DefaultDomain domain; + + domain = new DefaultDomain(); + domain.addGroup("group1"); + assertFalse(domain.contains(player1)); + assertTrue(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group1"); + domain.addGroup("group2"); + assertFalse(domain.contains(player1)); + assertTrue(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group1"); + domain.addGroup("group3"); + assertFalse(domain.contains(player1)); + assertTrue(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group3"); + assertFalse(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addPlayer(player1.getName()); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertFalse(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group3"); + domain.addPlayer(player1.getName()); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group3"); + domain.addPlayer(player1.getUniqueId()); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group3"); + domain.addPlayer(player1.getName()); + domain.addPlayer(player1.getUniqueId()); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addGroup("group3"); + domain.addPlayer(player1); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertTrue(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addPlayer(player1); + assertTrue(domain.contains(player1)); + assertFalse(domain.contains(player2)); + assertFalse(domain.contains(player3)); + + domain = new DefaultDomain(); + domain.addPlayer(player2); + domain.addPlayer(player3); + assertFalse(domain.contains(player1)); + assertTrue(domain.contains(player2)); + assertTrue(domain.contains(player3)); + } +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/ApplicableRegionSetTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/ApplicableRegionSetTest.java new file mode 100644 index 000000000..6824339e1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/ApplicableRegionSetTest.java @@ -0,0 +1,650 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StringFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.util.command.CommandFilter; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ApplicableRegionSetTest { + + @Test + public void testWildernessBuild() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(player, Flags.BUILD)); + } + + @Test + public void testWildernessBuildWithGlobalRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(player, Flags.BUILD)); + } + + @Test + public void testWildernessBuildWithRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testWildernessFlags() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + + assertTrue(set.testState(player, Flags.MOB_DAMAGE)); + assertTrue(set.testState(player, Flags.ENTRY)); + assertTrue(set.testState(player, Flags.EXIT)); + assertTrue(set.testState(player, Flags.LEAF_DECAY)); + assertTrue(set.testState(player, Flags.RECEIVE_CHAT)); + assertTrue(set.testState(player, Flags.SEND_CHAT)); + assertFalse(set.testState(player, Flags.INVINCIBILITY)); + + assertTrue(set.testState(player, Flags.BUILD)); + } + + @Test + public void testWildernessFlagsWithGlobalRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + + ApplicableRegionSet set = mock.getApplicableSet(); + + assertTrue(set.testState(player, Flags.MOB_DAMAGE)); + assertTrue(set.testState(player, Flags.ENTRY)); + assertTrue(set.testState(player, Flags.EXIT)); + assertTrue(set.testState(player, Flags.LEAF_DECAY)); + assertTrue(set.testState(player, Flags.RECEIVE_CHAT)); + assertTrue(set.testState(player, Flags.SEND_CHAT)); + assertFalse(set.testState(player, Flags.INVINCIBILITY)); + + assertTrue(set.testState(player, Flags.BUILD)); + } + + @Test + public void testFlagsWithRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + + assertTrue(set.testState(member, Flags.MOB_DAMAGE)); + assertTrue(set.testState(member, Flags.ENTRY)); + assertTrue(set.testState(member, Flags.EXIT)); + assertTrue(set.testState(member, Flags.LEAF_DECAY)); + assertTrue(set.testState(member, Flags.RECEIVE_CHAT)); + assertTrue(set.testState(member, Flags.SEND_CHAT)); + assertFalse(set.testState(member, Flags.INVINCIBILITY)); + + assertTrue(set.testState(member, Flags.BUILD)); + + assertTrue(set.testState(nonMember, Flags.MOB_DAMAGE)); + assertTrue(set.testState(nonMember, Flags.ENTRY)); + assertTrue(set.testState(nonMember, Flags.EXIT)); + assertTrue(set.testState(nonMember, Flags.LEAF_DECAY)); + assertTrue(set.testState(nonMember, Flags.RECEIVE_CHAT)); + assertTrue(set.testState(nonMember, Flags.SEND_CHAT)); + assertFalse(set.testState(nonMember, Flags.INVINCIBILITY)); + + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testStateFlagPriorityFallThrough() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + StateFlag state1 = new StateFlag(null, false); + StateFlag state2 = new StateFlag(null, false); + StateFlag state3 = new StateFlag(null, false); + + region = mock.add(0); + region.setFlag(state1, StateFlag.State.ALLOW); + region.setFlag(state2, StateFlag.State.DENY); + + region = mock.add(1); + region.setFlag(state1, StateFlag.State.DENY); + region.setFlag(state3, StateFlag.State.ALLOW); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(null, state1)); + assertFalse(set.testState(null, state2)); + assertTrue(set.testState(null, state3)); + } + + @Test + public void testNonStateFlagPriorityFallThrough() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + StringFlag string1 = new StringFlag(null); + StringFlag string2 = new StringFlag(null); + StringFlag string3 = new StringFlag(null); + StringFlag string4 = new StringFlag(null); + + region = mock.add(0); + region.setFlag(string1, "Beans"); + region.setFlag(string2, "Apples"); + + region = mock.add(1); + region.setFlag(string1, "Cats"); + region.setFlag(string3, "Bananas"); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertEquals(set.queryValue(null, string1), "Cats"); + assertEquals(set.queryValue(null, string2), "Apples"); + assertEquals(set.queryValue(null, string3), "Bananas"); + assertNull(set.queryValue(null, string4)); + } + + @Test + public void testStateFlagMultiplePriorityFallThrough() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + StringFlag string1 = new StringFlag(null); + StringFlag string2 = new StringFlag(null); + StringFlag string3 = new StringFlag(null); + StringFlag string4 = new StringFlag(null); + + region = mock.add(0); + region.setFlag(string1, "Beans"); + region.setFlag(string2, "Apples"); + region.setFlag(string3, "Dogs"); + + region = mock.add(1); + region.setFlag(string1, "Cats"); + region.setFlag(string3, "Bananas"); + + region = mock.add(10); + region.setFlag(string3, "Strings"); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertEquals(set.queryValue(null, string1), "Cats"); + assertEquals(set.queryValue(null, string2), "Apples"); + assertEquals(set.queryValue(null, string3), "Strings"); + assertNull(set.queryValue(null, string4)); + } + + @Test + public void testStateGlobalDefault() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + StateFlag state1 = new StateFlag(null, false); + StateFlag state2 = new StateFlag(null, false); + StateFlag state3 = new StateFlag(null, false); + StateFlag state4 = new StateFlag(null, true); + StateFlag state5 = new StateFlag(null, true); + StateFlag state6 = new StateFlag(null, true); + + region = mock.global(); + region.setFlag(state1, StateFlag.State.ALLOW); + region.setFlag(state2, StateFlag.State.DENY); + region.setFlag(state4, StateFlag.State.ALLOW); + region.setFlag(state5, StateFlag.State.DENY); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(null, state1)); + assertFalse(set.testState(null, state2)); + assertFalse(set.testState(null, state3)); + assertTrue(set.testState(null, state4)); + assertFalse(set.testState(null, state5)); + assertTrue(set.testState(null, state6)); + } + + @Test + public void testStateGlobalWithRegionsDefault() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + StateFlag state1 = new StateFlag(null, false); + StateFlag state2 = new StateFlag(null, false); + StateFlag state3 = new StateFlag(null, false); + StateFlag state4 = new StateFlag(null, true); + StateFlag state5 = new StateFlag(null, true); + StateFlag state6 = new StateFlag(null, true); + + region = mock.global(); + region.setFlag(state1, StateFlag.State.ALLOW); + region.setFlag(state2, StateFlag.State.DENY); + region.setFlag(state4, StateFlag.State.ALLOW); + region.setFlag(state5, StateFlag.State.DENY); + + region = mock.add(0); + region.setFlag(state1, StateFlag.State.DENY); + region.setFlag(state2, StateFlag.State.DENY); + region.setFlag(state4, StateFlag.State.DENY); + region.setFlag(state5, StateFlag.State.DENY); + + region = mock.add(1); + region.setFlag(state5, StateFlag.State.ALLOW); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(null, state1)); + assertFalse(set.testState(null, state2)); + assertFalse(set.testState(null, state3)); + assertFalse(set.testState(null, state4)); + assertTrue(set.testState(null, state5)); + assertTrue(set.testState(null, state6)); + } + + @Test + public void testBuildAccess() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testBuildRegionPriorities() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer upperMember = mock.createPlayer(); + LocalPlayer lowerMember = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(lowerMember); + + region = mock.add(1); + region.getOwners().addPlayer(upperMember); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(upperMember, Flags.BUILD)); + assertFalse(set.testState(lowerMember, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testBuildDenyFlag() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testBuildAllowFlag() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertTrue(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testHigherPriorityOverrideBuildDenyFlag() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + region = mock.add(1); + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertTrue(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testHigherPriorityUnsetBuildDenyFlag() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + region = mock.add(1); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testPriorityDisjointBuildDenyFlagAndMembership() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + region = mock.add(1); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testPriorityDisjointBuildDenyFlagAndRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + region = mock.add(1); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testPriorityDisjointMembershipAndBuildDenyFlag() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.add(0); + region.getOwners().addPlayer(member); + + region = mock.add(1); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testNoGlobalRegionDefaultBuild() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertTrue(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionDefaultBuild() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + @SuppressWarnings("unused") + ProtectedRegion region = mock.global(); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertTrue(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionBuildFlagAllow() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertTrue(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionBuildFlagDeny() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionBuildFlagAllowWithRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + + region = mock.add(0); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionBuildFlagDenyWithRegion() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + + region = mock.add(0); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionHavingOwnershipBuildFlagUnset() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionHavingOwnershipBuildFlagAllow() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.ALLOW); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertTrue(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionHavingOwnershipBuildFlagDeny() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer member = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + region.setFlag(Flags.BUILD, StateFlag.State.DENY); + region.getOwners().addPlayer(member); + + ApplicableRegionSet set = mock.getApplicableSet(); + assertFalse(set.testState(member, Flags.BUILD)); + assertFalse(set.testState(nonMember, Flags.BUILD)); + } + + @Test + public void testGlobalRegionCommandBlacklistWithRegionWhitelist() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + ProtectedRegion region; + + LocalPlayer nonMember = mock.createPlayer(); + + region = mock.global(); + Set blocked = new HashSet<>(); + blocked.add("/deny"); + blocked.add("/strange"); + region.setFlag(Flags.BLOCKED_CMDS, blocked); + + region = mock.add(0); + Set allowed = new HashSet<>(); + allowed.add("/permit"); + allowed.add("/strange"); + region.setFlag(Flags.ALLOWED_CMDS, allowed); + + ApplicableRegionSet set; + CommandFilter test; + + set = mock.getApplicableSet(); + test = new CommandFilter( + set.queryValue(nonMember, Flags.ALLOWED_CMDS), + set.queryValue(nonMember, Flags.BLOCKED_CMDS)); + assertTrue(test.apply("/permit")); + assertTrue(test.apply("/strange")); + assertFalse(test.apply("/other")); + assertFalse(test.apply("/deny")); + + set = mock.getApplicableSetInWilderness(); + test = new CommandFilter( + set.queryValue(nonMember, Flags.ALLOWED_CMDS), + set.queryValue(nonMember, Flags.BLOCKED_CMDS)); + assertTrue(test.apply("/permit")); + assertFalse(test.apply("/strange")); + assertTrue(test.apply("/other")); + assertFalse(test.apply("/deny")); + } + + @Test + public void testRegionSetReturnsNullForUnsetState() { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + mock.add(0); + mock.add(1); + assertNull(mock.getApplicableSet().queryState(null, Flags.INTERACT)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorMapFlagTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorMapFlagTest.java new file mode 100644 index 000000000..d38a5d79b --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorMapFlagTest.java @@ -0,0 +1,123 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.MapFlag; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StringFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SuppressWarnings({"UnusedDeclaration"}) +public class FlagValueCalculatorMapFlagTest { + @Test + public void testGetEffectiveMapFlagWithFallback() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, StateFlag.State.DENY); + MapFlag mapFlag = + new MapFlag<>("test", new StringFlag(null), new StateFlag(null, true)); + Map map = new HashMap<>(); + map.put("allow", StateFlag.State.ALLOW); + map.put("deny", StateFlag.State.DENY); + global.setFlag(mapFlag, map); + + ApplicableRegionSet applicableSet = mock.getApplicableSet(); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "allow", Flags.BUILD), + StateFlag.State.ALLOW); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "deny", Flags.BUILD), + StateFlag.State.DENY); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "undefined", Flags.BUILD), + StateFlag.State.DENY); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "allow", null), + StateFlag.State.ALLOW); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "deny", null), + StateFlag.State.DENY); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "undefined", null), + StateFlag.State.ALLOW); + } + + @Test + public void testGetEffectiveMapFlagWithSamePriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region1 = mock.add(0); + ProtectedRegion region2 = mock.add(0); + + MapFlag mapFlag = + new MapFlag<>("test", new StringFlag(null), new StateFlag(null, true)); + Map map1 = new HashMap<>(); + Map map2 = new HashMap<>(); + + map1.put("should-deny", StateFlag.State.ALLOW); + map2.put("should-deny", StateFlag.State.DENY); + + map1.put("should-allow", StateFlag.State.ALLOW); + + map1.put("should-allow2", StateFlag.State.ALLOW); + map2.put("should-allow2", StateFlag.State.ALLOW); + + region1.setFlag(mapFlag, map1); + region2.setFlag(mapFlag, map2); + + ApplicableRegionSet applicableSet = mock.getApplicableSet(); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "should-deny", null), + StateFlag.State.DENY); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "should-allow", null), + StateFlag.State.ALLOW); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "should-allow2", null), + StateFlag.State.ALLOW); + } + + + @Test + public void testGetEffectiveMapFlagWithInheritance() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent = mock.add(0); + ProtectedRegion child = mock.add(0, parent); + + MapFlag mapFlag = + new MapFlag<>("test", new StringFlag(null), new StateFlag(null, true)); + Map parentMap = new HashMap<>(); + Map childMap = new HashMap<>(); + + parentMap.put("useChildValue", StateFlag.State.ALLOW); + childMap.put("useChildValue", StateFlag.State.DENY); + + parentMap.put("useParentValue", StateFlag.State.ALLOW); + + parent.setFlag(mapFlag, parentMap); + child.setFlag(mapFlag, childMap); + + ApplicableRegionSet applicableSet = mock.getApplicableSet(); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "useChildValue", null), + StateFlag.State.DENY); + assertEquals(applicableSet.queryMapValue(null, mapFlag, "useParentValue", null), + StateFlag.State.ALLOW); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorTest.java new file mode 100644 index 000000000..b7eea3d04 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/FlagValueCalculatorTest.java @@ -0,0 +1,1970 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.protection.FlagValueCalculator.Result; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.StateFlag.State; +import com.sk89q.worldguard.protection.flags.StringFlag; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.ImmutableSet.copyOf; +import static com.google.common.collect.ImmutableSet.of; +import static org.junit.jupiter.api.Assertions.*; + +@SuppressWarnings({"UnusedAssignment", "UnusedDeclaration"}) +public class FlagValueCalculatorTest { + + @Test + public void testGetMembershipWilderness() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.NO_REGIONS); + } + + @Test + public void testGetMembershipWildernessWithGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer player = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.NO_REGIONS); + } + + @Test + public void testGetMembershipGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.NO_REGIONS); + } + + @Test + public void testGetMembershipGlobalRegionAndRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + + ProtectedRegion region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + } + + @Test + public void testGetMembershipPassthroughRegions() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.NO_REGIONS); + } + + @Test + public void testGetMembershipPassthroughAndRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOf() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.SUCCESS); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfAndAnotherNot() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + region = mock.add(0); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + + // Add another player (should still fail) + region.getMembers().addPlayer(mock.createPlayer()); + + result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfAndAnotherNotWithHigherPriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + region = mock.add(10); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfWithHigherPriorityAndAnotherNot() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + region = mock.add(10); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + region = mock.add(0); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.SUCCESS); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfWithAnotherParent() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion passthrough = mock.add(0); + passthrough.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + ProtectedRegion parent = mock.add(0); + + ProtectedRegion region = mock.add(0); + region.setParent(parent); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.SUCCESS); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfWithAnotherChild() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion passthrough = mock.add(0); + passthrough.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + ProtectedRegion parent = mock.add(0); + + ProtectedRegion region = mock.add(0); + region.setParent(parent); + + LocalPlayer player = mock.createPlayer(); + parent.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.SUCCESS); + } + + @Test + public void testGetMembershipPassthroughAndRegionMemberOfWithAnotherChildAndAnother() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion passthrough = mock.add(0); + passthrough.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + ProtectedRegion parent = mock.add(0); + + ProtectedRegion region = mock.add(0); + region.setParent(parent); + + region = mock.add(0); + + LocalPlayer player = mock.createPlayer(); + parent.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.FAIL); + } + + @Test + public void testGetMembershipThirdPriorityLower() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion passthrough = mock.add(0); + passthrough.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + ProtectedRegion parent = mock.add(0); + + ProtectedRegion region = mock.add(0); + region.setParent(parent); + + region = mock.add(0); + region.setPriority(-5); + + LocalPlayer player = mock.createPlayer(); + parent.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getMembership(player), Result.SUCCESS); + } + + // ======================================================================== + // ======================================================================== + + @Test + public void testQueryStateWilderness() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + StateFlag flag2 = new StateFlag("test2", true); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryState(null, flag1)); + assertEquals(result.queryState(null, flag2), State.ALLOW); + } + + // ======================================================================== + // ======================================================================== + + @Test + public void testQueryValueSingleRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + StateFlag flag2 = new StateFlag("test2", false); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag2, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(null, flag1)); + assertEquals(result.queryValue(null, flag2), State.DENY); + } + + @Test + public void testQueryValueDenyOverridesAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + StateFlag flag2 = new StateFlag("test2", false); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag2, State.DENY); + + region = mock.add(0); + region.setFlag(flag2, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(null, flag1)); + assertEquals(result.queryValue(null, flag2), State.DENY); + } + + @Test + public void testQueryValueAllowOverridesNone() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + + StateFlag flag1 = new StateFlag("test1", false); + StateFlag flag2 = new StateFlag("test2", false); + + region = mock.add(0); + region.setFlag(flag2, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(null, flag1)); + assertEquals(result.queryValue(null, flag2), State.ALLOW); + } + + @Test + public void testQueryValueMultipleFlags() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + StateFlag flag2 = new StateFlag("test2", false); + StateFlag flag3 = new StateFlag("test3", false); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag1, State.DENY); + region.setFlag(flag2, State.ALLOW); + + region = mock.add(0); + region.setFlag(flag2, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag1), State.DENY); + assertEquals(result.queryValue(null, flag2), State.DENY); + assertNull(result.queryValue(null, flag3)); + } + + @Test + public void testQueryValueFlagsWithRegionGroupsAndInheritance() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.setFlag(flag1, State.DENY); + parent.setFlag(flag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(member); + region.setParent(parent); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, flag1), State.DENY); + assertNull(result.queryValue(member, flag1)); + } + + @Test + public void testQueryValueFlagsWithRegionGroupsAndInheritanceAndParentMember() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberTwo = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.getMembers().addPlayer(memberOne); + parent.setFlag(flag1, State.DENY); + parent.setFlag(flag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.setParent(parent); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, flag1), State.DENY); + assertNull(result.queryValue(memberOne, flag1)); + assertEquals(result.queryValue(memberTwo, flag1), State.DENY); + } + + @Test + public void testQueryValueFlagsWithRegionGroupsAndPriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion lower = mock.add(-1); + lower.setFlag(flag1, State.DENY); + lower.setFlag(flag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(member); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, flag1), State.DENY); + assertEquals(result.queryValue(member, flag1), State.DENY); + } + + @Test + public void testQueryValueFlagsWithRegionGroupsAndPriorityAndOveride() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag1 = new StateFlag("test1", false); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion lower = mock.add(-1); + lower.setFlag(flag1, State.DENY); + lower.setFlag(flag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag1, State.ALLOW); + region.setFlag(flag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + region.getMembers().addPlayer(member); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, flag1), State.DENY); + assertEquals(result.queryValue(member, flag1), State.ALLOW); + } + + @Test + public void testQueryValueStringFlag() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + StateFlag flag1 = new StateFlag("test1", false); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertTrue(List.of("test1", "test2").contains(result.queryValue(null, stringFlag1))); + assertNull(result.queryValue(null, stringFlag2)); + assertNull(result.queryValue(null, flag1)); + } + + @Test + public void testQueryValueEmptyGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag = new StateFlag("test", true); + + ProtectedRegion global = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), State.ALLOW); + } + + @Test + public void testQueryValueGlobalRegionAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag = new StateFlag("test", true); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), State.ALLOW); + } + + @Test + public void testQueryValueGlobalRegionDeny() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StateFlag flag = new StateFlag("test", true); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), State.DENY); + } + + @Test + public void testQueryValueStringFlagWithGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag flag = new StringFlag("test"); + + ProtectedRegion global = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(null, flag)); + } + + @Test + public void testQueryValueStringFlagWithGlobalRegionValueSet() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag flag = new StringFlag("test"); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, "hello"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), "hello"); + } + + @Test + public void testQueryValueStringFlagWithGlobalRegionAndRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag flag = new StringFlag("test"); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, "hello"); + + ProtectedRegion region = mock.add(0); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), "hello"); + } + + @Test + public void testQueryValueStringFlagWithGlobalRegionAndRegionOverride() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag flag = new StringFlag("test"); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, "hello"); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag, "beep"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), "beep"); + } + + @Test + public void testQueryValueStringFlagWithEverything() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag flag = new StringFlag("test", RegionGroup.ALL); + + ProtectedRegion global = mock.global(); + global.setFlag(flag, "hello"); + + ProtectedRegion parent = mock.add(0); + parent.setFlag(flag, "ello there"); + + ProtectedRegion region = mock.add(0); + region.setFlag(flag, "beep beep"); + region.setFlag(flag.getRegionGroupFlag(), RegionGroup.MEMBERS); + region.setParent(parent); + + LocalPlayer nonMember = mock.createPlayer(); + + LocalPlayer member = mock.createPlayer(); + region.getMembers().addPlayer(member); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(null, flag), "ello there"); + assertEquals(result.queryValue(nonMember, flag), "ello there"); + assertEquals(result.queryValue(member, flag), "beep beep"); + } + + @Test + public void testQueryValueBuildFlagWilderness() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegionDeny() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.DENY); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegionAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegionMembership() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(member); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(member, Flags.BUILD), State.ALLOW); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegionMembershipAndDeny() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(member); + global.setFlag(Flags.BUILD, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(member, Flags.BUILD), State.DENY); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.DENY); + } + + @Test + public void testQueryValueBuildFlagWildernessAndGlobalRegionMembershipAndAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(member); + global.setFlag(Flags.BUILD, State.ALLOW); + + // Cannot set ALLOW on BUILD + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(member, Flags.BUILD), State.ALLOW); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + } + + @Test + public void testQueryValueBuildFlagRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer member = mock.createPlayer(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(member); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertEquals(result.queryValue(member, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlapping() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingDifferingPriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + region.setPriority(10); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingInheritanceFromParent() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.getMembers().addPlayer(memberOne); + parent.getMembers().addPlayer(memberBoth); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.setParent(parent); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingInheritanceFromChild() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.getMembers().addPlayer(memberBoth); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.getMembers().addPlayer(memberOne); + region.setParent(parent); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingInheritanceFromChildAndPriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.getMembers().addPlayer(memberBoth); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.getMembers().addPlayer(memberOne); + region.setParent(parent); + + ProtectedRegion priority = mock.add(10); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertNull(result.queryValue(memberBoth, Flags.BUILD)); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingInheritanceFromChildAndPriorityPassthrough() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion parent = mock.add(0); + parent.getMembers().addPlayer(memberBoth); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.getMembers().addPlayer(memberOne); + region.setParent(parent); + + ProtectedRegion priority = mock.add(10); + priority.setFlag(Flags.PASSTHROUGH, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionDenyRegionOverride() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.DENY); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.setFlag(Flags.BUILD, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.ALLOW); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionDenyRegionOverrideDenyAndAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.DENY); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + region.setFlag(Flags.BUILD, State.DENY); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + region.setFlag(Flags.BUILD, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.queryValue(nonMember, Flags.BUILD), State.DENY); + assertEquals(result.queryValue(memberOne, Flags.BUILD), State.DENY); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.DENY); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.ALLOW); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + // Disable setting ALLOW for safety reasons + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionMembership() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer globalMember = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(globalMember); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(globalMember, Flags.BUILD)); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionMembershipAndGlobalDeny() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer globalMember = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(globalMember); + global.setFlag(Flags.BUILD, State.DENY); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + // Inconsistent due to legacy reasons + assertNull(result.queryValue(globalMember, Flags.BUILD)); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + @Test + public void testQueryValueBuildFlagRegionsOverlappingAndGlobalRegionMembershipAndGlobalAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + LocalPlayer globalMember = mock.createPlayer(); + LocalPlayer nonMember = mock.createPlayer(); + LocalPlayer memberOne = mock.createPlayer(); + LocalPlayer memberBoth = mock.createPlayer(); + + ProtectedRegion global = mock.global(); + global.getMembers().addPlayer(globalMember); + global.setFlag(Flags.BUILD, State.ALLOW); + + ProtectedRegion region = mock.add(0); + region.getMembers().addPlayer(memberOne); + region.getMembers().addPlayer(memberBoth); + + region = mock.add(0); + region.getMembers().addPlayer(memberBoth); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.queryValue(globalMember, Flags.BUILD)); + assertNull(result.queryValue(nonMember, Flags.BUILD)); + assertNull(result.queryValue(memberOne, Flags.BUILD)); + assertEquals(result.queryValue(memberBoth, Flags.BUILD), State.ALLOW); + } + + // ======================================================================== + // ======================================================================== + + @Test + public void testQueryAllValuesTwoWithSamePriority() throws Exception { + // ==================================================================== + // Two regions with the same priority + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1", "test2")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesTwoWithDuplicateFlagValues() throws Exception { + // ==================================================================== + // Two regions with duplicate values + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test"); + + region = mock.add(0); + region.setFlag(stringFlag1, "test"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesWithHigherPriority() throws Exception { + // ==================================================================== + // One of the regions has a higher priority (should override) + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesWithTwoElevatedPriorities() throws Exception { + // ==================================================================== + // Two regions with the same elevated priority + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test3"); + + region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1", "test3")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentChildWithSamePriority() throws Exception { + // ==================================================================== + // Child region and parent region with the same priority + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(10); + parent1.setFlag(stringFlag1, "test3"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + region.setParent(parent1); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentWithHigherPriority() throws Exception { + // ==================================================================== + // Parent region with a higher priority than the child + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(20); + parent1.setFlag(stringFlag1, "test3"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + region.setParent(parent1); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test3")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentWithLowerPriority() throws Exception { + // ==================================================================== + // Parent region with a lower priority than the child + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(5); + parent1.setFlag(stringFlag1, "test3"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + region.setParent(parent1); + + region = mock.add(0); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesThirdRegionWithHigherPriorityThanParentChild() throws Exception { + // ==================================================================== + // Third region with higher priority than parent and child + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(5); + parent1.setFlag(stringFlag1, "test3"); + + ProtectedRegion region = mock.add(10); + region.setFlag(stringFlag1, "test1"); + region.setParent(parent1); + + region = mock.add(20); + region.setFlag(stringFlag1, "test2"); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test2")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentsAndInheritance() throws Exception { + // ==================================================================== + // Multiple regions with parents, one region using flag from parent + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(5); + parent1.setFlag(stringFlag1, "test1"); + + ProtectedRegion region = mock.add(20); + region.setFlag(stringFlag1, "test2"); + region.setParent(parent1); + + ProtectedRegion parent2 = mock.add(6); + parent2.setFlag(stringFlag1, "test3"); + + region = mock.add(20); + region.setParent(parent2); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test2", "test3")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentsAndInheritanceHighPriorityAndNoFlag() throws Exception { + // ==================================================================== + // Multiple regions with parents, one region with high priority but no flag + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + StateFlag flag1 = new StateFlag("test1", false); + + ProtectedRegion parent1 = mock.add(5); + parent1.setFlag(stringFlag1, "test1"); + + ProtectedRegion region = mock.add(20); + region.setFlag(stringFlag1, "test2"); + region.setParent(parent1); + + ProtectedRegion parent2 = mock.add(6); + parent2.setFlag(stringFlag1, "test3"); + + region = mock.add(30); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test2")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + @Test + public void testQueryAllValuesParentWithSamePriorityAsHighest() throws Exception { + // ==================================================================== + // As before, except a parent region has the same priority as the previous highest + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + StateFlag flag1 = new StateFlag("test1", false); + + ProtectedRegion parent1 = mock.add(30); + parent1.setFlag(stringFlag1, "test1"); + + ProtectedRegion region = mock.add(20); + region.setFlag(stringFlag1, "test2"); + region.setParent(parent1); + + ProtectedRegion parent2 = mock.add(6); + parent2.setFlag(stringFlag1, "test3"); + + region = mock.add(30); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(copyOf(result.queryAllValues(null, stringFlag1)), Set.of("test1")); + assertTrue(result.queryAllValues(null, stringFlag2).isEmpty()); + } + + // ======================================================================== + // ======================================================================== + + @Test + public void testGetEffectivePriority() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(30); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getPriority(region), 30); + } + + @Test + public void testGetEffectivePriorityGlobalRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getPriority(region), Integer.MIN_VALUE); + } + + // ======================================================================== + // ======================================================================== + + @Test + public void testGetEffectiveFlagSingleRegion() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + // ==================================================================== + // Single region + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagWithALLGroupAndNonMember() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + // ==================================================================== + // Single region with group ALL and non-member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagWithALLGroupAndNull() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + // ==================================================================== + // Single region with group ALL and null player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONMEMBERSGroupNonMember() throws Exception { + // ==================================================================== + // Single region with group NON-MEMBERS and non-member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONMEMBERSGroupNull() throws Exception { + // ==================================================================== + // Single region with group NON-MEMBERS and null player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONOWNERSGroupNonMember() throws Exception { + // ==================================================================== + // Single region with group NON-OWNERS and non-member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_OWNERS); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagMEMBERSGroupNonMember() throws Exception { + // ==================================================================== + // Single region with group MEMBERS and non-member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(region, stringFlag1, player)); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagMEMBERSGroupNull() throws Exception { + // ==================================================================== + // Single region with group MEMBERS and null player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(region, stringFlag1, null)); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagMEMBERSGroupMember() throws Exception { + // ==================================================================== + // Single region with group MEMBERS and member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagMEMBERSGroupOwner() throws Exception { + // ==================================================================== + // Single region with group MEMBERS and owner player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + + LocalPlayer player = mock.createPlayer(); + region.getOwners().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagOWNERSGroupOwner() throws Exception { + // ==================================================================== + // Single region with group OWNERS and owner player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.OWNERS); + + LocalPlayer player = mock.createPlayer(); + region.getOwners().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagOWNERSGroupMember() throws Exception { + // ==================================================================== + // Single region with group OWNERS and member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.OWNERS); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(region, stringFlag1, player)); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONOWNERSGroupOwner() throws Exception { + // ==================================================================== + // Single region with group NON-OWNERS and owner player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_OWNERS); + + LocalPlayer player = mock.createPlayer(); + region.getOwners().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(region, stringFlag1, player)); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONMEMBERSGroupOwner() throws Exception { + // ==================================================================== + // Single region with group NON-MEMBERS and owner player + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + + LocalPlayer player = mock.createPlayer(); + region.getOwners().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(region, stringFlag1, player)); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONOWNERSGroupMember() throws Exception { + // ==================================================================== + // Single region with group NON-OWNERS and member player + // ==================================================================== + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_OWNERS); + + LocalPlayer player = mock.createPlayer(); + region.getMembers().addPlayer(player); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagNONOWNERSNonMember() throws Exception { + // ==================================================================== + // Single region with group NON-OWNERS and non-member player + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "test1"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.NON_OWNERS); + + LocalPlayer player = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagThreeInheritance() throws Exception { + // ==================================================================== + // Three-level inheritance + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "test1"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test1"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagThreeInheritanceMiddleOverride() throws Exception { + // ==================================================================== + // Three-level inheritance, overridden on middle level + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "test1"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setParent(parent1); + parent2.setFlag(stringFlag1, "test2"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test2"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagThreeInheritanceLastOverride() throws Exception { + // ==================================================================== + // Three-level inheritance, overridden on last level + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "test1"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + region.setFlag(stringFlag1, "test3"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "test3"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagInheritanceAndDifferingGroups() throws Exception { + // ==================================================================== + // Three-level inheritance, overridden on last level + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "everyone"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setFlag(stringFlag1, "members"); + parent2.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + + LocalPlayer player1 = mock.createPlayer(); + parent2.getMembers().addPlayer(player1); + + LocalPlayer player2 = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player1), "members"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player2), "everyone"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "everyone"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagInheritanceAndDifferingGroupsMemberOnChild() throws Exception { + // ==================================================================== + // Three-level inheritance, overridden on last level + // ==================================================================== + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "everyone"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setFlag(stringFlag1, "members"); + parent2.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + + LocalPlayer player1 = mock.createPlayer(); + region.getMembers().addPlayer(player1); + + LocalPlayer player2 = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player1), "members"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player2), "everyone"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "everyone"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagInheritanceAndDifferingGroupsMemberOnParent() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "everyone"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setFlag(stringFlag1, "members"); + parent2.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setParent(parent2); + + LocalPlayer player1 = mock.createPlayer(); + parent1.getMembers().addPlayer(player1); + + LocalPlayer player2 = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player1), "members"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player2), "everyone"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "everyone"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagInheritanceAndDifferingGroupsMemberOnParentFlagOnBottom() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.add(0); + parent1.setFlag(stringFlag1, "everyone"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "members"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + region.setParent(parent2); + + LocalPlayer player1 = mock.createPlayer(); + parent1.getMembers().addPlayer(player1); + + LocalPlayer player2 = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player1), "members"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player2), "everyone"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "everyone"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagInheritanceAndDifferingGroupsMemberOnParentFlagOnBottomGroupOutside() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + StringFlag stringFlag1 = new StringFlag("string1"); + StringFlag stringFlag2 = new StringFlag("string2"); + + ProtectedRegion parent1 = mock.createOutside(0); + parent1.setFlag(stringFlag1, "everyone"); + parent1.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.ALL); + + ProtectedRegion parent2 = mock.add(0); + parent2.setParent(parent1); + + ProtectedRegion region = mock.add(0); + region.setFlag(stringFlag1, "members"); + region.setFlag(stringFlag1.getRegionGroupFlag(), RegionGroup.MEMBERS); + region.setParent(parent2); + + LocalPlayer player1 = mock.createPlayer(); + parent1.getMembers().addPlayer(player1); + + LocalPlayer player2 = mock.createPlayer(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player1), "members"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, player2), "everyone"); + assertEquals(result.getEffectiveFlag(region, stringFlag1, null), "everyone"); + assertNull(result.getEffectiveFlag(region, stringFlag2, null)); + } + + @Test + public void testGetEffectiveFlagGlobalRegionBuild() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + + FlagValueCalculator result = mock.getFlagCalculator(); + assertNull(result.getEffectiveFlag(global, Flags.BUILD, null)); + } + + @Test + public void testGetEffectiveFlagGlobalRegionBuildDeny() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.DENY); + + FlagValueCalculator result = mock.getFlagCalculator(); + // Cannot let users override BUILD on GLOBAL + assertEquals(result.getEffectiveFlag(global, Flags.BUILD, null), State.DENY); + } + + @Test + public void testGetEffectiveFlagGlobalRegionBuildAllow() throws Exception { + MockApplicableRegionSet mock = new MockApplicableRegionSet(); + + ProtectedRegion global = mock.global(); + global.setFlag(Flags.BUILD, State.ALLOW); + + FlagValueCalculator result = mock.getFlagCalculator(); + // Cannot let users override BUILD on GLOBAL + assertNull(result.getEffectiveFlag(global, Flags.BUILD, null)); + } +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java new file mode 100644 index 000000000..b0268cf22 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexPriorityTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class HashMapIndexPriorityTest extends RegionPriorityTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new HashMapIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java new file mode 100644 index 000000000..3009c9296 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRegionOverlapTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class HashMapIndexRegionOverlapTest extends RegionOverlapTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new HashMapIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRemovalTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRemovalTest.java new file mode 100644 index 000000000..b2534a181 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexRemovalTest.java @@ -0,0 +1,111 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.RemovalStrategy; +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion.CircularInheritanceException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HashMapIndexRemovalTest { + private static final String ORPHAN_ID = "orphan"; + private static final String NESTED_ID_PREFIX = "nested_"; + private static final int NEST_DEPTH = 5; // minimum 3 + private RegionManager manager; + + private FlagRegistry getFlagRegistry() { + return WorldGuard.getInstance().getFlagRegistry(); + } + + private RegionManager createRegionManager() { + return new RegionManager(new MemoryRegionDatabase(), new HashMapIndex.Factory(), getFlagRegistry()); + } + + @BeforeEach + public void setUp() { + manager = createRegionManager(); + + setUpOrphanRegion(); + setUpDeeplyNestedRegions(); + } + + private void setUpDeeplyNestedRegions() { + ProtectedRegion parent = null; + for (int i = 0; i < NEST_DEPTH; i++) { + ProtectedRegion newRegion = new ProtectedCuboidRegion(NESTED_ID_PREFIX + i, + BlockVector3.ZERO, BlockVector3.ZERO); // bounds don't matter for this test + if (parent != null) { + try { + newRegion.setParent(parent); + } catch (CircularInheritanceException ignored) { + } + } + parent = newRegion; + } + manager.addRegion(parent); + } + + private void setUpOrphanRegion() { + ProtectedRegion orphan = new ProtectedCuboidRegion(ORPHAN_ID, + BlockVector3.ZERO, BlockVector3.at(5, 5, 5)); + manager.addRegion(orphan); + } + + @Test + public void testRemoveWithUnset() { + int initialSize = 1 + NEST_DEPTH; // orphan + nested + assertEquals(initialSize, manager.size()); + manager.removeRegion(NESTED_ID_PREFIX + "0", RemovalStrategy.UNSET_PARENT_IN_CHILDREN); + assertEquals(initialSize - 1, manager.size()); + assertFalse(manager.hasRegion(NESTED_ID_PREFIX + "0")); + + final ProtectedRegion firstChild = manager.getRegion(NESTED_ID_PREFIX + "1"); + assertNotNull(firstChild); + assertNull(firstChild.getParent()); + + final ProtectedRegion secondChild = manager.getRegion(NESTED_ID_PREFIX + "2"); + assertNotNull(secondChild); + assertEquals(secondChild.getParent(), firstChild); + } + + @Test + public void testRemoveWithChildren() { + int initialSize = 1 + NEST_DEPTH; // orphan + nested + assertEquals(manager.size(), initialSize); + manager.removeRegion(NESTED_ID_PREFIX + "1", RemovalStrategy.REMOVE_CHILDREN); + assertEquals(2, manager.size()); + assertTrue(manager.hasRegion(NESTED_ID_PREFIX + "0")); + assertTrue(manager.hasRegion(ORPHAN_ID)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java new file mode 100644 index 000000000..4a77f7c68 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/HashMapIndexTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.index.HashMapIndex; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class HashMapIndexTest extends RegionOverlapTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new HashMapIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java new file mode 100644 index 000000000..dee6e3280 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/MockApplicableRegionSet.java @@ -0,0 +1,99 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.LocalPlayer; +import com.sk89q.worldguard.TestPlayer; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import com.sk89q.worldguard.protection.util.NormativeOrders; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class MockApplicableRegionSet { + + private List regions = new ArrayList<>(); + private ProtectedRegion global; + private int id = 0; + private int playerIndex = 0; + + public void add(ProtectedRegion region) { + regions.add(region); + } + + public LocalPlayer createPlayer() { + playerIndex++; + LocalPlayer player = new TestPlayer("#PLAYER_" + playerIndex); + return player; + } + + public ProtectedRegion global() { + global = new GlobalProtectedRegion("__global__"); + return global; + } + + public ProtectedRegion createOutside(int priority) { + ProtectedRegion region = new ProtectedCuboidRegion(getNextId(), + BlockVector3.at(0, 0, 0), BlockVector3.at(1, 1, 1)); + region.setPriority(priority); + return region; + } + + public ProtectedRegion add(int priority) { + ProtectedRegion region = new ProtectedCuboidRegion(getNextId(), + BlockVector3.at(0, 0, 0), BlockVector3.at(1, 1, 1)); + region.setPriority(priority); + add(region); + return region; + } + + public ProtectedRegion add(int priority, ProtectedRegion parent) + throws ProtectedRegion.CircularInheritanceException { + ProtectedRegion region = new ProtectedCuboidRegion(getNextId(), + BlockVector3.at(0, 0, 0), BlockVector3.at(1, 1, 1)); + region.setPriority(priority); + region.setParent(parent); + add(region); + return region; + } + + public ApplicableRegionSet getApplicableSetInWilderness() { + return new RegionResultSet(Collections.emptyList(), global); + } + + public ApplicableRegionSet getApplicableSet() { + return new RegionResultSet(regions, global); + } + + public FlagValueCalculator getFlagCalculator() { + NormativeOrders.sort(regions); + return new FlagValueCalculator(regions, global); + } + + private String getNextId() { + id++; + return "REGION_" + id; + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java new file mode 100644 index 000000000..faf8aa06c --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeIndexTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class PriorityRTreeIndexTest extends RegionOverlapTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new PriorityRTreeIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java new file mode 100644 index 000000000..5849dacf3 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionEntryExitTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class PriorityRTreeRegionEntryExitTest extends RegionEntryExitTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new PriorityRTreeIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java new file mode 100644 index 000000000..6acef3bd1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionOverlapTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class PriorityRTreeRegionOverlapTest extends RegionOverlapTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new PriorityRTreeIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java new file mode 100644 index 000000000..ad8cad7ca --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/PriorityRTreeRegionPriorityTest.java @@ -0,0 +1,33 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldguard.protection.managers.index.PriorityRTreeIndex; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.managers.storage.MemoryRegionDatabase; + +public class PriorityRTreeRegionPriorityTest extends RegionPriorityTest { + + @Override + protected RegionManager createRegionManager() throws Exception { + return new RegionManager(new MemoryRegionDatabase(), new PriorityRTreeIndex.Factory(), getFlagRegistry()); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java new file mode 100644 index 000000000..35f574917 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionEntryExitTest.java @@ -0,0 +1,152 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + + +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.TestPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.RegionGroup; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class RegionEntryExitTest { + + static String ENTRY_ID = "entry_rg"; + static String EXIT_ID = "exit_rg"; + static String BUILDER_GROUP = "builder"; + static String VIP_GROUP = "vip"; + + BlockVector3 inEntry = BlockVector3.at(5, 64, 5); + BlockVector3 inExit = BlockVector3.at(-5, 65, -5); + + RegionManager manager; + ProtectedRegion globalRegion; + ProtectedRegion entryRegion; + ProtectedRegion exitRegion; + TestPlayer vipPlayer; + TestPlayer builderPlayer; + + protected FlagRegistry getFlagRegistry() { + return WorldGuard.getInstance().getFlagRegistry(); + } + + protected abstract RegionManager createRegionManager() throws Exception; + + @BeforeEach + public void setUp() throws Exception { + setUpGlobalRegion(); + + manager = createRegionManager(); + + setUpPlayers(); + setUpEntryRegion(); + setUpExitRegion(); + } + + void setUpPlayers() { + vipPlayer = new TestPlayer("dudu"); + vipPlayer.addGroup(VIP_GROUP); + + builderPlayer = new TestPlayer("esskay"); + builderPlayer.addGroup(BUILDER_GROUP); + + // @Test + // assertFalse(builderPlayer.wuvs(vipPlayer)); // causes test to fail + } + + void setUpGlobalRegion() { + globalRegion = new GlobalProtectedRegion("__global__"); + } + + void setUpEntryRegion() { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(VIP_GROUP); + + ProtectedRegion region = new ProtectedCuboidRegion(ENTRY_ID, BlockVector3.at(1, 0, 1), BlockVector3.at(10, 255, 10)); + + region.setMembers(domain); + manager.addRegion(region); + + entryRegion = region; + // this is the way it's supposed to work + // whatever the group flag is set to is the group that the flag APPLIES to + // in this case, non members (esskay) should be DENIED entry + entryRegion.setFlag(Flags.ENTRY, StateFlag.State.DENY); + entryRegion.setFlag(Flags.ENTRY.getRegionGroupFlag(), RegionGroup.NON_MEMBERS); + } + + void setUpExitRegion() throws Exception { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(BUILDER_GROUP); + + ProtectedRegion region = new ProtectedCuboidRegion(EXIT_ID, BlockVector3.at(-1, 0, -1), BlockVector3.at(-10, 255, -10)); + + region.setOwners(domain); + manager.addRegion(region); + + entryRegion = region; + // same as above + entryRegion.setFlag(Flags.EXIT, StateFlag.State.DENY); + entryRegion.setFlag(Flags.EXIT.getRegionGroupFlag(), RegionGroup.NON_OWNERS); + } + + @Test + public void testEntry() throws Exception { + ApplicableRegionSet appl; + + appl = manager.getApplicableRegions(inEntry); +// ProtectedRegion rg = appl.iterator().next(); +// System.out.println("rg " + rg.getId()); +// System.out.println("mem " + rg.getMembers().toGroupsString()); +// System.out.println("flag " + appl.getFlag(Flags.ENTRY)); +// System.out.println("grp " + appl.getFlag(Flags.ENTRY.getRegionGroupFlag())); +// System.out.println("==="); + assertTrue(appl.testState(vipPlayer, Flags.ENTRY), "Allowed Entry"); + assertFalse(appl.testState(builderPlayer, Flags.ENTRY), "Forbidden Entry"); + } + + @Test + public void testExit() throws Exception { + ApplicableRegionSet appl; + + appl = manager.getApplicableRegions(inExit); +// ProtectedRegion rg = appl.iterator().next(); +// System.out.println("rg " + rg.getId()); +// System.out.println("own " + rg.getOwners().toGroupsString()); +// System.out.println("flag " + appl.getFlag(Flags.EXIT)); +// System.out.println("grp " + appl.getFlag(Flags.EXIT.getRegionGroupFlag())); +// System.out.println("==="); + assertTrue(appl.testState(builderPlayer, Flags.EXIT), "Allowed Exit"); + assertFalse(appl.testState(vipPlayer, Flags.EXIT), "Forbidden Exit"); + } + +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionOverlapTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionOverlapTest.java new file mode 100644 index 000000000..c9955b296 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionOverlapTest.java @@ -0,0 +1,257 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.TestPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.association.RegionOverlapAssociation; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class RegionOverlapTest { + static String COURTYARD_ID = "courtyard"; + static String FOUNTAIN_ID = "fountain"; + static String NO_FIRE_ID = "nofire"; + static String MEMBER_GROUP = "member"; + static String COURTYARD_GROUP = "courtyard"; + + BlockVector3 inFountain = BlockVector3.at(2, 2, 2); + BlockVector3 inCourtyard = BlockVector3.at(7, 7, 7); + BlockVector3 outside = BlockVector3.at(15, 15, 15); + BlockVector3 inNoFire = BlockVector3.at(150, 150, 150); + RegionManager manager; + ProtectedRegion globalRegion; + ProtectedRegion courtyard; + ProtectedRegion fountain; + TestPlayer player1; + TestPlayer player2; + + /* @Parameterized.Parameters(name = "{index}: useMaxPrio = {0}") + public static Iterable params() { + return Arrays.asList(new Object[][]{{true}, {false}}); + }*/ + + + // public boolean useMaxPriorityAssociation; + + protected FlagRegistry getFlagRegistry() { + return WorldGuard.getInstance().getFlagRegistry(); + } + + protected abstract RegionManager createRegionManager() throws Exception; + + @BeforeAll + public void setUp() throws Exception { + setUpGlobalRegion(); + + manager = createRegionManager(); + + setUpPlayers(); + setUpCourtyardRegion(); + setUpFountainRegion(); + setUpNoFireRegion(); + } + + void setUpPlayers() { + player1 = new TestPlayer("tetsu"); + player1.addGroup(MEMBER_GROUP); + player1.addGroup(COURTYARD_GROUP); + + player2 = new TestPlayer("alex"); + player2.addGroup(MEMBER_GROUP); + } + + void setUpGlobalRegion() { + globalRegion = new GlobalProtectedRegion("__global__"); + } + + void setUpCourtyardRegion() { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(COURTYARD_GROUP); + + ArrayList points = new ArrayList<>(); + points.add(BlockVector2.ZERO); + points.add(BlockVector2.at(10, 0)); + points.add(BlockVector2.at(10, 10)); + points.add(BlockVector2.at(0, 10)); + + //ProtectedRegion region = new ProtectedCuboidRegion(COURTYARD_ID, new BlockVector(0, 0, 0), new BlockVector(10, 10, 10)); + ProtectedRegion region = new ProtectedPolygonalRegion(COURTYARD_ID, points, 0, 10); + + region.setOwners(domain); + region.setPriority(5); + manager.addRegion(region); + + courtyard = region; + } + + void setUpFountainRegion() throws Exception { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(MEMBER_GROUP); + + ProtectedRegion region = new ProtectedCuboidRegion(FOUNTAIN_ID, + BlockVector3.ZERO, BlockVector3.at(5, 5, 5)); + region.setMembers(domain); + region.setPriority(10); + manager.addRegion(region); + + fountain = region; + fountain.setParent(courtyard); + fountain.setFlag(Flags.FIRE_SPREAD, StateFlag.State.DENY); + } + + void setUpNoFireRegion() throws Exception { + ProtectedRegion region = new ProtectedCuboidRegion(NO_FIRE_ID, + BlockVector3.at(100, 100, 100), BlockVector3.at(200, 200, 200)); + manager.addRegion(region); + region.setFlag(Flags.FIRE_SPREAD, StateFlag.State.DENY); + } + + @Test + public void testNonBuildFlag() { + ApplicableRegionSet appl; + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(null, Flags.FIRE_SPREAD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(appl.testState(null, Flags.FIRE_SPREAD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertFalse(appl.testState(null, Flags.FIRE_SPREAD)); + + // Inside no fire zone + appl = manager.getApplicableRegions(inNoFire); + assertFalse(appl.testState(null, Flags.FIRE_SPREAD)); + } + + @Test + public void testPlayer1BuildAccess() { + ApplicableRegionSet appl; + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(player1, Flags.BUILD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(appl.testState(player1, Flags.BUILD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertTrue(appl.testState(player1, Flags.BUILD)); + } + + @Test + public void testPlayer2BuildAccess() { + ApplicableRegionSet appl; + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(player2, Flags.BUILD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertFalse(appl.testState(player2, Flags.BUILD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertTrue(appl.testState(player2, Flags.BUILD)); + } + + @ParameterizedTest(name = "useMaxPriorityAssociation={0}") + @ValueSource(booleans = { false, true }) + public void testNonPlayerBuildAccessInOneRegion(boolean useMaxPriorityAssociation) { + ApplicableRegionSet appl; + + HashSet source = new HashSet<>(); + source.add(courtyard); + RegionOverlapAssociation assoc = new RegionOverlapAssociation(source, useMaxPriorityAssociation); + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(assoc, Flags.BUILD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(appl.testState(assoc, Flags.BUILD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertTrue(appl.testState(assoc, Flags.BUILD)); + } + + @ParameterizedTest(name = "useMaxPriorityAssociation={0}") + @ValueSource(booleans = { false, true }) + public void testNonPlayerBuildAccessInBothRegions(boolean useMaxPriorityAssociation) { + ApplicableRegionSet appl; + + HashSet source = new HashSet<>(); + source.add(fountain); + source.add(courtyard); + RegionOverlapAssociation assoc = new RegionOverlapAssociation(source, useMaxPriorityAssociation); + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(assoc, Flags.BUILD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(useMaxPriorityAssociation ^ appl.testState(assoc, Flags.BUILD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertTrue(appl.testState(assoc, Flags.BUILD)); + } + + @ParameterizedTest(name = "useMaxPriorityAssociation={0}") + @ValueSource(booleans = { false, true }) + public void testNonPlayerBuildAccessInNoRegions(boolean useMaxPriorityAssociation) { + ApplicableRegionSet appl; + + HashSet source = new HashSet<>(); + RegionOverlapAssociation assoc = new RegionOverlapAssociation(source, useMaxPriorityAssociation); + + // Outside + appl = manager.getApplicableRegions(outside); + assertTrue(appl.testState(assoc, Flags.BUILD)); + // Inside courtyard + appl = manager.getApplicableRegions(inCourtyard); + assertFalse(appl.testState(assoc, Flags.BUILD)); + // Inside fountain + appl = manager.getApplicableRegions(inFountain); + assertFalse(appl.testState(assoc, Flags.BUILD)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionPriorityTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionPriorityTest.java new file mode 100644 index 000000000..78b711860 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/RegionPriorityTest.java @@ -0,0 +1,168 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection; + + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldguard.TestPlayer; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.domains.DefaultDomain; +import com.sk89q.worldguard.protection.flags.Flags; +import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.flags.registry.FlagRegistry; +import com.sk89q.worldguard.protection.managers.RegionManager; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; +import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; +import com.sk89q.worldguard.protection.regions.ProtectedPolygonalRegion; +import com.sk89q.worldguard.protection.regions.ProtectedRegion; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class RegionPriorityTest { + static String COURTYARD_ID = "courtyard"; + static String FOUNTAIN_ID = "fountain"; + static String NO_FIRE_ID = "nofire"; + static String MEMBER_GROUP = "member"; + static String COURTYARD_GROUP = "courtyard"; + + BlockVector3 inFountain = BlockVector3.at(2, 2, 2); + BlockVector3 inCourtyard = BlockVector3.at(7, 7, 7); + BlockVector3 outside = BlockVector3.at(15, 15, 15); + RegionManager manager; + ProtectedRegion globalRegion; + ProtectedRegion courtyard; + ProtectedRegion fountain; + TestPlayer player1; + TestPlayer player2; + + protected FlagRegistry getFlagRegistry() { + return WorldGuard.getInstance().getFlagRegistry(); + } + + protected abstract RegionManager createRegionManager() throws Exception; + + @BeforeAll + public void setUp() throws Exception { + setUpGlobalRegion(); + + manager = createRegionManager(); + + setUpPlayers(); + setUpCourtyardRegion(); + setUpFountainRegion(); + } + + void setUpPlayers() { + player1 = new TestPlayer("tetsu"); + player1.addGroup(MEMBER_GROUP); + player1.addGroup(COURTYARD_GROUP); + + player2 = new TestPlayer("alex"); + player2.addGroup(MEMBER_GROUP); + } + + void setUpGlobalRegion() { + globalRegion = new GlobalProtectedRegion("__global__"); + } + + void setUpCourtyardRegion() { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(COURTYARD_GROUP); + + ArrayList points = new ArrayList<>(); + points.add(BlockVector2.ZERO); + points.add(BlockVector2.at(10, 0)); + points.add(BlockVector2.at(10, 10)); + points.add(BlockVector2.at(0, 10)); + + //ProtectedRegion region = new ProtectedCuboidRegion(COURTYARD_ID, new BlockVector(0, 0, 0), new BlockVector(10, 10, 10)); + ProtectedRegion region = new ProtectedPolygonalRegion(COURTYARD_ID, points, 0, 10); + + region.setOwners(domain); + manager.addRegion(region); + + courtyard = region; + courtyard.setFlag(Flags.MOB_SPAWNING, StateFlag.State.DENY); + } + + void setUpFountainRegion() throws Exception { + DefaultDomain domain = new DefaultDomain(); + domain.addGroup(MEMBER_GROUP); + + ProtectedRegion region = new ProtectedCuboidRegion(FOUNTAIN_ID, + BlockVector3.ZERO, BlockVector3.at(5, 5, 5)); + region.setMembers(domain); + manager.addRegion(region); + + fountain = region; + fountain.setParent(courtyard); + fountain.setFlag(Flags.FIRE_SPREAD, StateFlag.State.DENY); + fountain.setFlag(Flags.MOB_SPAWNING, StateFlag.State.ALLOW); + } + + @Test + public void testNoPriorities() throws Exception { + ApplicableRegionSet appl; + + courtyard.setPriority(0); + fountain.setPriority(0); + + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(appl.testState(null, Flags.FIRE_SPREAD)); + assertFalse(appl.testState(null, Flags.MOB_SPAWNING)); + appl = manager.getApplicableRegions(inFountain); + assertFalse(appl.testState(null, Flags.FIRE_SPREAD)); + assertTrue(appl.testState(null, Flags.MOB_SPAWNING)); + } + + @Test + public void testPriorities() throws Exception { + ApplicableRegionSet appl; + + courtyard.setPriority(5); + fountain.setPriority(0); + + appl = manager.getApplicableRegions(inCourtyard); + assertTrue(appl.testState(null, Flags.FIRE_SPREAD)); + appl = manager.getApplicableRegions(inFountain); + assertFalse(appl.testState(null, Flags.FIRE_SPREAD)); + } + + @Test + public void testPriorities2() throws Exception { + ApplicableRegionSet appl; + + courtyard.setPriority(0); + fountain.setPriority(5); + + appl = manager.getApplicableRegions(inCourtyard); + assertFalse(appl.testState(null, Flags.MOB_SPAWNING)); + appl = manager.getApplicableRegions(inFountain); + assertTrue(appl.testState(null, Flags.MOB_SPAWNING)); + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/regions/RegionIntersectTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/regions/RegionIntersectTest.java new file mode 100644 index 000000000..4c739be8e --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/protection/regions/RegionIntersectTest.java @@ -0,0 +1,125 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.protection.regions; + + +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RegionIntersectTest { + + @Test + public void testCuboidGetIntersectingRegions() { + ProtectedRegion region = new ProtectedCuboidRegion("square", + BlockVector3.at(100, 40, 0), BlockVector3.at(140, 128, 40)); + + assertIntersection(region, new ProtectedCuboidRegion("normal", + BlockVector3.at(80, 40, -20), BlockVector3.at(120, 128, 20)), + true); + + assertIntersection(region, new ProtectedCuboidRegion("small", + BlockVector3.at(98, 45, 20), BlockVector3.at(103, 50, 25)), + true); + + assertIntersection(region, new ProtectedCuboidRegion("large", + BlockVector3.at(-500, 0, -600), BlockVector3.at(1000, 128, 1000)), + true); + + assertIntersection(region, new ProtectedCuboidRegion("short", + BlockVector3.at(50, 40, -1), BlockVector3.at(150, 128, 2)), + true); + + assertIntersection(region, new ProtectedCuboidRegion("long", + BlockVector3.at(0, 40, 5), BlockVector3.at(1000, 128, 8)), + true); + + List triangleOverlap = new ArrayList<>(); + triangleOverlap.add(BlockVector2.at(90, -10)); + triangleOverlap.add(BlockVector2.at(120, -10)); + triangleOverlap.add(BlockVector2.at(90, 20)); + + assertIntersection(region, new ProtectedPolygonalRegion("triangleOverlap", + triangleOverlap, 0, 128), + true); + + List triangleNoOverlap = new ArrayList<>(); + triangleNoOverlap.add(BlockVector2.at(90, -10)); + triangleNoOverlap.add(BlockVector2.at(105, -10)); + triangleNoOverlap.add(BlockVector2.at(90, 5)); + + assertIntersection(region, new ProtectedPolygonalRegion("triangleNoOverlap", + triangleNoOverlap, 0, 128), + false); + + List triangleOverlapNoPoints = new ArrayList<>(); + triangleOverlapNoPoints.add(BlockVector2.at(100, -10)); + triangleOverlapNoPoints.add(BlockVector2.at(120, 50)); + triangleOverlapNoPoints.add(BlockVector2.at(140, -20)); + + assertIntersection(region, new ProtectedPolygonalRegion("triangleOverlapNoPoints", + triangleOverlapNoPoints, 60, 80), + true); + } + + private void assertIntersection(ProtectedRegion region1, ProtectedRegion region2, boolean expected) { + boolean actual = false; + List regions = new ArrayList<>(); + regions.add(region2); + + try { + actual = (region1.getIntersectingRegions(regions).size() == 1); + } catch (Exception e) { + e.printStackTrace(); + } + + assertEquals(expected, actual, "Check for '" + region2.getId() + "' region failed."); + } + + private static final BlockVector2[] polygon = { + BlockVector2.at(1, 0), + BlockVector2.at(4, 3), + BlockVector2.at(4, -3), + }; + + @Test + public void testIntersection() throws Exception { + final ProtectedCuboidRegion cuboidRegion = new ProtectedCuboidRegion("cuboidRegion", BlockVector3.at(-3, -3, -3), BlockVector3.at(3, 3, 3)); + for (int angle = 0; angle < 360; angle += 90) { + final BlockVector2[] rotatedPolygon = new BlockVector2[polygon.length]; + for (int i = 0; i < polygon.length; i++) { + final BlockVector2 vertex = polygon[i]; + rotatedPolygon[i] = vertex.transform2D(angle, 0, 0, 0, 0); + } + + final ProtectedPolygonalRegion polygonalRegion = new ProtectedPolygonalRegion("polygonalRegion", Arrays.asList(rotatedPolygon), -3, 3); + + assertTrue(cuboidRegion.intersectsEdges(polygonalRegion), String.format("%s does not intersect (cuboid.intersectsEdges(polygonal)", Arrays.asList(rotatedPolygon))); + assertTrue(polygonalRegion.intersectsEdges(cuboidRegion), String.format("%s does not intersect (polygonal.intersectsEdges(cuboid)", Arrays.asList(rotatedPolygon))); + } + } +} diff --git a/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/util/CommandFilterTest.java b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/util/CommandFilterTest.java new file mode 100644 index 000000000..f6836bef1 --- /dev/null +++ b/sk89q-worldguard/worldguard-core/src/test/java/com/sk89q/worldguard/util/CommandFilterTest.java @@ -0,0 +1,196 @@ +/* + * WorldGuard, a suite of tools for Minecraft + * Copyright (C) sk89q + * Copyright (C) WorldGuard team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldguard.util; + +import com.sk89q.worldguard.util.command.CommandFilter; +import com.sk89q.worldguard.util.command.CommandFilter.Builder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CommandFilterTest { + + private static final String[] COMMAND_SEPARATORS = new String[] {" ", " ", "\t", " \t", "\n", "\r\n"}; + + @Test + public void testApply() throws Exception { + CommandFilter filter; + + // ==================================================================== + // No rules + // ==================================================================== + + filter = new Builder().build(); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/deny1", true); + assertSubcommands(filter, "/other", true); + + // ==================================================================== + // Root PERMIT + // ==================================================================== + + filter = new Builder() + .permit("/permit1", "/permit2") + .build(); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/permit2", true); + assertSubcommands(filter, "/other", false); + + // ==================================================================== + // Root DENY + // ==================================================================== + + filter = new Builder() + .deny("/deny1", "/deny2") + .build(); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/deny2", false); + assertSubcommands(filter, "/other", true); + + // ==================================================================== + // Root PERMIT + DENY no overlap + // ==================================================================== + + filter = new Builder() + .permit("/permit1", "/permit2") + .deny("/deny1", "/deny2") + .build(); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/deny2", false); + assertSubcommands(filter, "/other", false); + + // ==================================================================== + // Root PERMIT + DENY WITH overlap + // ==================================================================== + + filter = new Builder() + .permit("/permit1", "/permit2", "/strange") + .deny("/deny1", "/deny2", "/strange") + .build(); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/deny2", false); + assertSubcommands(filter, "/strange", true); + assertSubcommands(filter, "/other", false); + + // ==================================================================== + // Subcommand PERMIT + // ==================================================================== + + filter = new Builder() + .permit("/permit1", "/parent permit1", "/parent between subpermit1") + .build(); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/parent", false); + assertSubcommands(filter, "/parent permit1", true); + assertSubcommands(filter, "/parent other", false); + assertSubcommands(filter, "/parent between", false); + assertSubcommands(filter, "/parent between subpermit1", true); + assertSubcommands(filter, "/parent between other", false); + assertSubcommands(filter, "/parent between other subpermit1", false); + assertSubcommands(filter, "/other", false); + assertSubcommands(filter, "/other permit1", false); + assertSubcommands(filter, "/other between", false); + assertSubcommands(filter, "/other between subpermit1", false); + + // ==================================================================== + // Mixed DENY + // ==================================================================== + + filter = new Builder() + .deny("/deny1", "/parent deny1", "/parent between subdeny1") + .build(); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/parent", true); + assertSubcommands(filter, "/parent deny1", false); + assertSubcommands(filter, "/parent between", true); + assertSubcommands(filter, "/parent between subdeny1", false); + assertSubcommands(filter, "/parent between else", true); + assertSubcommands(filter, "/parent else", true); + assertSubcommands(filter, "/other", true); + assertSubcommands(filter, "/other deny1", true); + assertSubcommands(filter, "/other between", true); + assertSubcommands(filter, "/other between subdeny1", true); + assertSubcommands(filter, "/other between else", true); + + // ==================================================================== + // Mixed PERMIT + DENY no overlap + // ==================================================================== + + filter = new Builder() + .deny("/deny1", "/denyparent deny1", "/denyparent between subdeny1") + .permit("/permit1", "/permitparent permit1", "/permitparent between subpermit1") + .build(); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/denyparent", false); + assertSubcommands(filter, "/denyparent deny1", false); + assertSubcommands(filter, "/denyparent else", false); + assertSubcommands(filter, "/denyparent between", false); + assertSubcommands(filter, "/denyparent between subdeny1", false); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/permitparent", false); + assertSubcommands(filter, "/permitparent permit1", true); + assertSubcommands(filter, "/permitparent else", false); + assertSubcommands(filter, "/permitparent between", false); + assertSubcommands(filter, "/permitparent between subpermit1", true); + assertSubcommands(filter, "/other", false); + assertSubcommands(filter, "/other permit1", false); + assertSubcommands(filter, "/other between", false); + assertSubcommands(filter, "/other between subpermit1", false); + + // ==================================================================== + // Mixed PERMIT + DENY overlap + // ==================================================================== + + filter = new Builder() + .deny("/deny1", "/parent deny1", "/parent between subdeny1", "/parent between", "/parent between strange", "/parent between strange sub") + .permit("/permit1", "/parent permit1", "/parent between sub", "/parent between strange") + .build(); + assertSubcommands(filter, "/deny1", false); + assertSubcommands(filter, "/parent", false); + assertSubcommands(filter, "/parent deny1", false); + assertSubcommands(filter, "/parent else", false); + assertSubcommands(filter, "/parent permit1", true); + assertSubcommands(filter, "/parent between", false); + assertSubcommands(filter, "/parent between sub", true); + assertSubcommands(filter, "/parent between other", false); + assertSubcommands(filter, "/parent between strange", true); + assertSubcommands(filter, "/parent between strange sub", true); + assertSubcommands(filter, "/parent between strange other", true); + assertSubcommands(filter, "/permit1", true); + assertSubcommands(filter, "/permit1 deny1", true); + assertSubcommands(filter, "/other", false); + assertSubcommands(filter, "/other permit1", false); + assertSubcommands(filter, "/other between", false); + assertSubcommands(filter, "/other between subpermit1", false); + } + + private void assertSubcommands(CommandFilter filter, final String root, boolean expected) { + for (String separator : COMMAND_SEPARATORS) { + assertEquals(filter.apply(root.replaceAll(" ", separator)), expected); + assertEquals(filter.apply((root + " _subcmd").replaceAll(" ", separator)), expected); + assertEquals(filter.apply((root + " _subcmd _another").replaceAll(" ", separator)), expected); + } + } + +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-libs/build.gradle.kts b/sk89q-worldguard/worldguard-libs/build.gradle.kts new file mode 100644 index 000000000..33c839af1 --- /dev/null +++ b/sk89q-worldguard/worldguard-libs/build.gradle.kts @@ -0,0 +1,3 @@ +tasks.register("build") { + dependsOn(subprojects.map { it.tasks.named("build") }) +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-libs/bukkit/build.gradle.kts b/sk89q-worldguard/worldguard-libs/bukkit/build.gradle.kts new file mode 100644 index 000000000..388618cea --- /dev/null +++ b/sk89q-worldguard/worldguard-libs/bukkit/build.gradle.kts @@ -0,0 +1 @@ +applyLibrariesConfiguration() diff --git a/sk89q-worldguard/worldguard-libs/core/build.gradle.kts b/sk89q-worldguard/worldguard-libs/core/build.gradle.kts new file mode 100644 index 000000000..2551871c1 --- /dev/null +++ b/sk89q-worldguard/worldguard-libs/core/build.gradle.kts @@ -0,0 +1,18 @@ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar + +applyLibrariesConfiguration() + +dependencies { + "shade"("org.enginehub:squirrelid:${Versions.SQUIRRELID}") + "shade"("org.khelekore:prtree:1.5.0") +} + +tasks.named("jar") { + dependencies { + relocate("org.enginehub.squirrelid", "com.sk89q.worldguard.util.profile") { + include(dependency("org.enginehub:squirrelid")) + } + + include(dependency("org.khelekore:prtree")) + } +} \ No newline at end of file diff --git a/sk89q-worldguard/worldguard-logo.png b/sk89q-worldguard/worldguard-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e5c028a705167f20214ccc93ca5db6c4a8a02bde GIT binary patch literal 10220 zcmXY1Wmp_dvqg7-po<3Gg~cJkgR{6KxVyW%6Bf7N?oRMP0t88d1_|!&F2UU{dB0mf zdiv>SX1eNBP4#s3iBwXM#=;=QKtMpil9iEAK|nx^f9dO>0bbhFR)mU|%WF4DEjLw1 z3pY<=7jpzrGe;A1u&lkYrMZf^v6;7Xzqud+0)4Ekgs7U={GmR2HicxybIZNHt6A&^ zY$SLHE;b}PgB&*&B?y3uNT@A|mwMTGDW&E{=9$9}Gl0LbaPf;QaFoi0F>uZjdDktT>aeTh?nk~QPmpLTyrQ^0{;j&0n3xGP zf11lXKO}ztS01PjD_>&IQ4LeYR81_O^GPi;QF`%iq3=yt!Cx#jx$@dI`ow>epm1;` z0uy`T>hbSu5V#!@!o^VOP@}8resLe*O`;_BF(yk;mO(+tl0UW3(a|F#rNJ2Q%X9Bx ziuwo%K2qeFl9acK4KQk^$Jm{O#q#*WfFJ`_S&4tjh~Y)ZQ;G{!LehK$tS(VI5$L=F z_Y-E1btqCK+?XQW>`UyZr?f#Yf*%C4i-gRbgHr^TEfV2LY*A3$>KjL+{$weooRlnS z+Jqsa_z)=GKLt)dfW5PTDS{UlE=huuX!4}*E5z-$64V-;uyd&jX7C@qWZWH5#Ymx% zGA|M%jnx015=hwyjSG3XP04T$;ChbKpv8gMpvKQmizi1dzLZt$nHmxj((OePAJjCZ z!{p6^+E6pwDpx>{n?lGR(iMW{Ee>fOYUs?F~5WY*~ZB+blK1 zhJI?x7!{i^r4V)sa=$A7V(Km1lfV_u&ph`-lysUF3WfgWhtF<2<0ii7?j)MOP{cIC zrm(*HC1E{Tg*+taw#e8YjTTQv=6~3+fMgQpnHnb5I0>6~_+^71KiM!DxELmB+OA!4 zeH_xTbT5pq<*IuB&qPKfMit$XRPALAg}2P%OasXboy!vD*Cs3KsgBUtXe`DTsFB6y z{~r)kB*xFx(_(L$C(jyP-lC~RjbNdwfU%P#^j=}JcHObFP2Td7kgRF)ApJvoo)%a4 zOvXb*tlu=Fq_JM8vK`Mvzb*h@Svy7EOUT(z6XSNF;?2~zNOEAmf}Y*1ksRzFG9Yd# zuy=ovEiy^JD@J1Y`dJkd5p|U!QV*Gz6BK7eQCgHB{1d8d%;vUOy{Q0hQUApqb-n06 zhfMYcf{e(|Wx#LVK-gBpJ^d#WaWx_a-G>s@W|ecA~( zK~^gEe!D9_Y`%Z z9j5*@Ob3{thht_E4i|Gtncnxb%9K`3>t$c$yf)7KNld-+)%p%3e@{U;A*1?EerAk| zgzXoP26jwX#)X%%^!Np5<`W0@y{dq`p{2X!cb{&v%%7qY)5dSQxH27h?x|n{?uTfZ z0b&zNkaOYe(m&QZgY$Zv^2%2RoLrZ6SGR)l*Q4KLvFl7JHODUyGnbtnV@)MAFVVX* z{VR#Qp~RVhgF^OPtsRoaCIT9)9(E(qj)kn8T2z6%FUDR^Dn1A%R!p zKe7<%hRUGhuUoL$44L{4v%D#|E`^XXg^dzrL#0xStXleZZqOm1w7#7LXv9Q9f976OemgZ&Ikb4TG5u%7*F(H&dzpVrQYBl;a?oS)Y&WJ)tDNFQW?46QYgVeiQDsma2q=8@gUp15&qbmhCAb z_a-DG$*^-MC5^1g8n?mD7b+-;XKBx!TM0}>#(@c5k{08CWr8GJeqddj|f{UpnF| z(-_l~7h%<6_ryvCvcGnZ(mF#Xm!EL8gJG5p7dDe%q0b8Kv^oubO$)RsX2_B1NUKsYi94g)!if8W;9AY3JbTPl*ajNIFpMM z(;uN%k?ZU{t6&qmZ4gy(|C@Iv0C~kAb2dzD*Hb8LC8kO2`hq#%!3szlA2q0;=oFjx zn(fzD6gm<|toRj<%u?6kIl5MT`HgjBY8OE;3N#}PEz&|*ZFqmK@)Uv3cs9hz0RQBO>SL0A zjRpK~<}n`cG0`-gFm~#XBjrqu;4QHL^aH9&p_*2>EEOmb_91`M`3|M>t)9#!fxIfR zVY#kEz2((RyAl~V@gOu_7;xSb4bX?vRkKm<>>|w7CWQy^=<^+Sd&vtu1S^(Y{t;d& z@0^SPND_$sX=5%jxXzefysoi{^^U-3G!`O>1alzVTj&F;^*u!&S0~~t1o*Ae@%8%K z(%c7Kv^pX2uaGqeL1-g6X_hdY!k?XY0YEJSbB0S}l6aJ)4S>}=aN37>viTS2L|gbb zTI0;6K!w3H?3Bq~FWs9@;3Fv+F1z5XHTEk~Pa%Qk)dncX*g>?z_QDm}nxMMiLy=JffLcT1Ap)0 zI049iIYD;9)2;G{HfPLZ6$|$C6e!~`mS0&mub6qCorr(AI5IQ}4nnIj2c;9Q$H1KO zrSzXAv)dd@f;Wl7%0mk?@wx)ZS=p3UIw#*FL(sb&;faVeO##5E63zS1y#aJG;MrJ( ztBjCaJhUr&wPt@diQBrIX8|qjw}N0izpllU2g(@y`Qk; zGjEB-0}0=EwaEtm!Ybx{S~JyCm(puc8B75&F*J}cQk2A^FuUq8z7ct%T$^Wz1^ZmR zW^>^TrHU`F2bAU@>T}ivNw`>&1oJ!c{0f<{N(=qWDpHPm^v3;VEogkshgtfhV1=k6 zCBL#XTd=+hUA+xhryOoit)F|447$|^qKCJ+u1<8dpDYJj9H*7;7RP({k@`xb}v~(=4Z_9(yLd_Ema%Yy|hwf zW_Npsnt2pfs!a1-26>P6BsPMC>w8P0Ghrtd=hAhEd@&W}7T!fZMu@l=V6p}a`dW?WA4SW?j>`iCF*h{(L}0EV_NGL zPgz%}=8Kqz{V-`n(NvPi1WahKTt1oEqil6^;FO}*eIKHcgVwZEIP7$gP|lDMo3{eFQm+$j&|ZecX` z8xqj1=NmU~S6LjxM_%YBt{=BtOiM#08-~-SgT)f*-af&e*^0HZdLvZ@PBf1VRcVpfXMf;R;tPHAZR@&R zuncSMq^3;-#uLd=W{HjrBeEJNppqu-@y_!w%hfd7B=Rj$g%^7B98sw%=&1K5nJo2j30;2nmLqVT#ncy}s@^1}kfuhZhVhE;nOiK5kC zPBlFTqv;QtrS-NhgS%d_2s0Wo;9q9wKU(S78)nneqxKNUb`8s2z7i2Jao+wGC{WyTyX`6sb8>LbTVudcIV5HxNDk>$pY zulmn{C?0CT%;8JoLN=OGvN zuf2X%?}V06#FIk~N*h!`-jVOA-5i#<&yh#Re_+sgKB0$^Du9&DFTV`$5Z~M6M#Gl> zy8cDvkvS(~dgFwLHWFws`~(4gJs?lJ81LslW1{rK-@d}R+E#e!Yyt0*wb;!Rz5u!d;A9{97 z-#mqq%oSDHTT!$Eh#dFJQsql61{j1_7Wt?14dwgAs|$)rLOik)>xtBC!;Evw>Y^B0 zc*-VLuz(#P>CQWv;z&7{1=vg+V#9a-_aK_|ciVnRa$wFO|4GymTmv)RvK{h@(gzdu zYSX+p*E6@4rs?44*sy@xQ)onO;K!T05f}bVJTRr!cqEzrCL*N`e5#AfiDH=Vi52I} zY5g2>{3mO6i`OPajt$hx71r@A+R>ng(&Q35g03CQT<{PeA)qaCg7IbG)x-RszE<+T zK;}$@vbA*N^f9FJ*y?0Ki!4s79_~)Y;Uh~)m|*E#15hmAD_VD0)hin3a3Wc3mj050^XzEdx#-U8JcYUQQ{PfY3F6HbE2K|$^gMeQ8cs&4k%0Ilkk;8KM!+KwBw9`PMtoS{~y!xe5kJct@RI`n6xDDFT zT?^_%S8E<7>a(NBlvLsnZ}!(Xmvqd~@0!F;1DdfBqr1CJrvxqQQHRnas^}>0!HcLG zgEeCaiS>qT1M*;^_lNz)B;DNctBCfhz)HPvMNK8j<(9Z!)cct?*lBRsKD%JGhO`sh@@|^U9ounVVMeu-vPKXO4*coGGfX2i9f- zvDO-t?Lt&}zp9P>`1?$`zTB44hymIypYD!W&S9|+eg?m`K&a}z|3rU!2pa5ddMm}? zt!IPfVn9mIlSD)jEZ~CRYpqLO4>as?@je zsA!aKv+PePzRRyykM9`fofBD7d>b1BUpYJG_Db22lq8X=%cnC< zN6&TJ646AtF#Fr%WdA0m5fq7-_uzj-4{2X~uxk#wE+0eu{&>-HxJ*f*LmrLpnylyp zz#1Q~lC-U^`;Y^Ica7BkQE(hr8Y}MAx^i{A*dz6-^^d#%kig9aVpivzX4cX}MjGlpP2chKlUrQ6Z*$-&bG>`aGB8 zqQglWPp6C|y0P>}c)b0d_gV^l`*qyP7`DoAB@k=p8SvDx-WX!Hh^9P39?XgGiFV1q zSk4{vyE0OGHlQ)%Xtj1=5#dQxD~bC8o9N3EL1-OL%7j$Kc&?gjPSBpu`+=AcxPWN0 zS~RrqCixx4qE%0~ZqJVSIppqDpl`u>VgWK>mwJ1aUXbtnRn2U-@>a4cXMw$W*+{!! z5+|(oqOzH3sLks)A4eF!ANK*20l?RL!0h#gGV-`K;ZC~2CLNY++90GZiJ`I=cuDNK z%9O|zae&`#KY)))OR2SbvhFfd0%xikDox8G-GJ&5Qhmp&tzMOVXk+II9sC;Y%IY}H zX!ETn>TIEeMIcOvpth=TT#ckWx~cGIL(tB8IkPYFRm&PF?4!z>>olp`u!NyoeB=#P z3|Cc<-kHJZ1aXEfEF#!brkql$vu9{aaB$E|Lx$lsSD^@I!rkTaUB~!LiG<0;2cbT* zv+{VwzR83t|g%`QssA9+&01^OhH}Zi><+~ z% zFjqA(XkvR}%r>oA10K&It0H+y-fQ57hN_;@0jfCyyAk2qY9l4w*ygKPjGANK_pQt!n!TMhD^J)JE=s8_2S{k<_8=%8vMI29Wx=i1~dpsgV( z5pwF4KPdD4Mj3PI#F;1u1z15xL+kHOK}$5*WeOD5Cv8`;1bxW;K19bTL^n#JAU?w6 zzCYd|=P>=W%?@HQw0o0u-SRT<^0WeN%Mo3{O||+r{H^HSc44bM?XJQzK*hzqc(P9S zZ#*|+QqtvjXxKVif$$v#!oCfc;nRdvm=}L9OSsyj$CJ2XS#y^J7+EPIR0Ep?`7j2- zn|e);R7_>7{45m>B!=7dky*NhxFZ2vV?mC7GQe72&3)CC;fGWQgiS&5V!-zpB;`SP zTJ^wi?iewSe&li7yn5W^T@SD}Zlk#pQpV&j?DhUM*bGu-{X22CG#N}ZhWfKRc;FUp zN{Cn3v!hEaO^03=pQY|6ZjxMu#4@>cyzTYCV87ApEBaiyU_||1$;L4K*M-RgCZQ%8x@CebILx}7X&OHgm=Y0&e3OmFL$)JmOa zLa)Q!>?k~Ir2Gzy0KHNro$L@Mi-$>Aa_Ay&GVkLxCb*JcjyEOqh27n>^38}KPc1Pj zxzQbm6J~F`R0%&44jfy@HRk7ODqSrE9^-}H`~K>S@T~~P0E*vlP`9nTTEuDcxzu6q zK1UPCl?USg4h5wv9}B)T|(RrphVO=vnG6ZA%r^QZwtW};HsMR ztQ|qvaIaxt!#MQoaFw)tv}*5d4-LR`6&a4$thKYKsM~3k0Y8hxikYsK4>}p$zj{A= zO{zuzZ1{!8UW|L((!WN!mQzjO30~UUuHxIHMe)R`wjCCwjO=g%u_yandP{i!1R^_8 z)^`J;oFm+4#z8}tT?4LA?2zwgp`F;DBiF{qwmBHPzdCMwIC8q*9jUwCzHkt;jAJJ8 z=9-yp57}43{O!eGCRCVn;xX;venozB-y9^Dt&)3)*UDv&KG#jEH@~b0V2{lz~=0RA!`Q!D3?eNnp~uTWz$)tmO?v{`FXd+jZ=S zHlfeTp(ig{HP9pd_>)+)SFHPdlK-$b@p~SpFM2&_D8LFzL~1Xp_fx+|=GspwJI><( z&f!3M;XNAGW78zE1P;AHm6Fxa_LmJH56O!CTQW!j7f$HBf!B@hs$)XT%WuM$cS?_w zjBdEaP!4}ehSM_Eu9nT1LS?E0nzWw4NZANQ>$1($Y*{+F_MY}k?ij9XVa&PR97e1PBp)_pzhF*{noKq8d` z<(lO(?JT`g3ty_N-`D&r&FT$0&06doEVUXBfPl%;MQA>4t{K1tJ2CfUo5Ir{?+ED7CoR97iev)l%f3cLeKO(W$vMT!_F)f%hkRoqh zVbnYmVUBn-i#qYB&Zv>C7>xsQvZO_W9>`;=L(8b!@g8i8q`|15e%R8t35d$G+7apq zIqhv%Uk`(|8_aK?QSHp@wG1l0!2n;ikY{iyRdMHso}|oh-rljlxE3Q3tuLKG^63L~ zp>Pe2wTAq)<5W^BN@DRZxD*ccQWS2plGeO%uM&)rK&Ik@N;N8L4f=P0?||dS*7$4j z4Zl%$Wrk3-LL$F9xl054heo9GD|v>ceFzvnJN9l;CR0L}Xq>7Mj$);=DIXzU*EYoj z!NK%T8GvAOrrcfa|ep2CR>! z+AFIB3lk&me_FlQ#(0?Oc34E27CxEs!A?eUI_l5FIJ-gB;tBB;)4NARtI3n54@O<3 z!fMfbTs-0Wst81Nt*o2m5Uz7LLd zAu&fI11?hqKL2$FY76DpUHnlZznwpL9lT#K_8zzt7gx4tY4rNnfs1tHs8kD#_kXvJ3K(JXnXMI^}D`|uM$D8D5jR6a&)_$ z6q1_)u3g0#m2eA0{$WSy>>{L|Hg{7#==+4tqrgtM4y7N{rIbzQw^dU5Nb3 zuU7B{E2Co>eMHinNB*el0k3qu6VdL+%VCkl4oS&b&D{?G=p9R29TVcQ`4_okPeyQjWidn0s*Jk>4c zZ9qo}cG=(PQ6oW~Kt6mufXCw6)q z8!)!fZHZIk&TjAT-2iXr9xqr-VPF(+j@kb~fy4>DBpWR0?P80ju+$gI#Ajjn?1!*Z z&)==a{vtGS@63`C{*7Y8XA{x((cZ;O@QZ0S-nQc?&_N&&_5$2;6=50b_Z-@$rX2Px zYTQ_#thRNof~rTQs#z_sH7H)UP=4y2jFtExl#&qfLPQAZX2Xr>#}xUlsPEP_1mscR zx3j|g%r3_y2WLH76C%ii>pDM>Pt(El1SQ+z(z0!(la-w4JNi0)%s<5$ayLAt;#4LI zzAz4;VHq&6>0i#7#!VxZCd7-)pogpPQSz8kw(G}GeBwF=jAXgDqWxWgiS`!JeVtFA z2(EPj#4T-oePBsEIR7XB31Hj_nl|+@<9SFlWGWI`KMKW@WW|k?CyG+J%SsEO zh9>a#i8?2t+#7uBD*TZEB>0&X&T(-^Qp1*q!)X7u`?5ka;Xi%_T<4M;J?oCXG#6E8 zsdOA627`v6;l%o*CKq#gY{&u4;N}D6>688^g0nt)%cm>Pp3B`RR`0{$*-6O@SI6D< zh<``?i7+O*1kCQTTtpiaU10)2XUbmDEZXzd;16dj+`VNQyavgFv3w<@g#;_idE{ak ze^(H7fZg*9-c~8$^#A-6kD>yJWXM;QH50aq#4;+**;Em{f2(5M480#XB zRWpk!3Z#SQZx;M5*>uK85j03k4F6|@6+T*L>>ysA7;VCh)TpYGUAR+%j4&TGk{anm zHfrD4)Oo3M6RYf0&@@5XZJ@vOZXo`@2PN!rBi7@2*HsmO+^3&qsl(RJ{DQ{Kuur0{ z>+1V8G`-0=XCKj8n;iZcKk-Y@j}$@2&E+VglK=CgO)$)EQ?}TiF-3wBKw}?Co8hNcLKIK3AID()qZ)g*wKgf0`17d>IjB#CJ1SmxL0A*dxvS&UO4)P^&i@~fB0CWcP-3Sz?yx|{h=(97DvmA94Yh*ViJX;)u7%rjm?M9JpqfI_kKW{{_35ug+ zSi0#<`goY~Kisrva1p{iiBcrK?b4LWcgs?jOuLDm1$JI`OHxMF;}=#R<$w17U)26) zf`6aV#%-9?4jTserP?*5Dl6pl1dLG4%-u?@`Kal_&oCr(h*i%1hirE`9j+5P(P{rq z5U3y#jRtSx&3cTsWSc!HPb4obwvh^O7T~(|0_OQ%k(?v*zmRqo2TMZ5Xz{pz{#LWx zl9hP-xuCsznFFKWT7-SJS>uL)$@|;4tVE#9**`o<3{lph304OfcPrww`z)&RTVJes z^W*)$Nw?R`GgqwhtyJu3|9$Na=*2Ri3_oFpI_`KfCY}(1CI54Y!n9n|H!5ZT z*LZ;Ie;g)7a1XQ|X>yjS535C{lQooA|KpzHcV1;>52Z6+Nz%c8d?7jzoSc#BEi+mf-mt_cUaCMU+U63;@g}2O?C!ibh#JO%m*maOK2=(>zI7nlb6(c^Hb8{ z=Qec*F@rZtK_2o8P%o1H`9CYpF_`2YIxrhz@m!ZGSlS;2zgdDUYR~)no0$GfR_IVg zY77VU`+&Pg7VxI99BwOvbH0C3aKH+nC4kimi)3LSQP51rAsset 2 \ No newline at end of file -- GitLab