From 557f09a4dece921e7bf54f83c06e12fb785bc393 Mon Sep 17 00:00:00 2001 From: bloodshot <jdroque@gmail.com> Date: Sun, 24 Nov 2019 17:01:05 -0500 Subject: [PATCH] Add sponge support. --- settings.gradle | 1 + sponge/build.gradle | 203 ++ sponge/gradle.properties | 11 + .../java/com/griefdefender/GDBootstrap.java | 228 ++ .../java/com/griefdefender/GDCatalogType.java | 76 + .../java/com/griefdefender/GDChatType.java | 49 + .../main/java/com/griefdefender/GDCore.java | 109 + .../java/com/griefdefender/GDDebugData.java | 224 ++ .../com/griefdefender/GDEventManager.java | 67 + .../java/com/griefdefender/GDPlayerData.java | 701 ++++ .../java/com/griefdefender/GDRelocator.java | 78 + .../java/com/griefdefender/GDTimings.java | 81 + .../java/com/griefdefender/GDVersion.java | 44 + .../griefdefender/GriefDefenderPlugin.java | 1166 ++++++ .../griefdefender/cache/EventResultCache.java | 78 + .../com/griefdefender/cache/MessageCache.java | 772 ++++ .../cache/PermissionHolderCache.java | 154 + .../claim/ClaimContextCalculator.java | 87 + .../java/com/griefdefender/claim/GDClaim.java | 3111 +++++++++++++++++ .../griefdefender/claim/GDClaimManager.java | 668 ++++ .../griefdefender/claim/GDClaimResult.java | 89 + .../griefdefender/claim/GDClaimSchematic.java | 187 + .../com/griefdefender/claim/GDClaimType.java | 119 + .../com/griefdefender/claim/GDShovelType.java | 49 + .../java/com/griefdefender/claim/GDTown.java | 21 + .../com/griefdefender/claim/GDTrustType.java | 53 + .../griefdefender/command/ClaimFlagBase.java | 973 ++++++ .../command/ClaimOptionBase.java | 913 +++++ .../command/ClaimSubjectType.java | 42 + .../CommandAdjustBonusClaimBlocks.java | 90 + .../command/CommandCallback.java | 47 + .../command/CommandClaimAbandon.java | 218 ++ .../command/CommandClaimAbandonAll.java | 188 + .../command/CommandClaimAbandonTop.java | 48 + .../command/CommandClaimAdmin.java | 52 + .../command/CommandClaimBan.java | 129 + .../command/CommandClaimBank.java | 76 + .../command/CommandClaimBasic.java | 52 + .../command/CommandClaimBuy.java | 97 + .../command/CommandClaimBuyBlocks.java | 143 + .../command/CommandClaimClear.java | 173 + .../command/CommandClaimContract.java | 201 ++ .../command/CommandClaimCreate.java | 146 + .../command/CommandClaimCuboid.java | 56 + .../command/CommandClaimDelete.java | 121 + .../command/CommandClaimDeleteAll.java | 110 + .../command/CommandClaimDeleteAllAdmin.java | 90 + .../command/CommandClaimDeleteTop.java | 49 + .../command/CommandClaimExpand.java | 201 ++ .../command/CommandClaimFarewell.java | 80 + .../command/CommandClaimFlag.java | 57 + .../command/CommandClaimFlagDebug.java | 67 + .../command/CommandClaimFlagGroup.java | 80 + .../command/CommandClaimFlagPlayer.java | 66 + .../command/CommandClaimFlagReset.java | 102 + .../command/CommandClaimGreeting.java | 81 + .../command/CommandClaimIgnore.java | 67 + .../command/CommandClaimInfo.java | 888 +++++ .../command/CommandClaimInherit.java | 69 + .../command/CommandClaimList.java | 246 ++ .../command/CommandClaimMode.java | 78 + .../command/CommandClaimName.java | 74 + .../command/CommandClaimOption.java | 85 + .../command/CommandClaimOptionGroup.java | 74 + .../command/CommandClaimOptionPlayer.java | 58 + .../command/CommandClaimPermissionGroup.java | 147 + .../command/CommandClaimPermissionPlayer.java | 144 + .../command/CommandClaimSchematic.java | 154 + .../command/CommandClaimSell.java | 129 + .../command/CommandClaimSellBlocks.java | 154 + .../command/CommandClaimSetSpawn.java | 66 + .../command/CommandClaimSpawn.java | 120 + .../command/CommandClaimSubdivision.java | 53 + .../command/CommandClaimTown.java | 53 + .../command/CommandClaimTransfer.java | 86 + .../command/CommandClaimUnban.java | 107 + .../command/CommandClaimWorldEdit.java | 52 + .../griefdefender/command/CommandDebug.java | 116 + .../command/CommandException.java | 92 + .../command/CommandGDReload.java | 48 + .../command/CommandGDVersion.java | 77 + .../command/CommandGiveBlocks.java | 92 + .../griefdefender/command/CommandGivePet.java | 31 + .../griefdefender/command/CommandHelp.java | 51 + .../griefdefender/command/CommandHelper.java | 1352 +++++++ .../command/CommandPagination.java | 36 + .../command/CommandPlayerInfo.java | 273 ++ .../command/CommandRestoreClaim.java | 96 + .../command/CommandRestoreNature.java | 56 + .../command/CommandSetAccruedClaimBlocks.java | 115 + .../command/CommandTownChat.java | 63 + .../griefdefender/command/CommandTownTag.java | 88 + .../command/CommandTrustGroup.java | 145 + .../command/CommandTrustGroupAll.java | 132 + .../command/CommandTrustList.java | 253 ++ .../command/CommandTrustPlayer.java | 167 + .../command/CommandTrustPlayerAll.java | 151 + .../command/CommandUntrustGroup.java | 115 + .../command/CommandUntrustGroupAll.java | 118 + .../command/CommandUntrustPlayer.java | 121 + .../command/CommandUntrustPlayerAll.java | 126 + .../command/ComponentMessageException.java | 104 + .../command/gphelper/CommandAccessTrust.java | 125 + .../gphelper/CommandContainerTrust.java | 125 + .../configuration/ClaimDataConfig.java | 552 +++ .../configuration/ClaimStorageData.java | 209 ++ .../configuration/ClaimTemplateConfig.java | 130 + .../configuration/ClaimTemplateStorage.java | 133 + .../configuration/EconomyDataConfig.java | 120 + .../configuration/GriefDefenderConfig.java | 230 ++ .../configuration/IClaimData.java | 80 + .../configuration/MessageDataConfig.java | 68 + .../configuration/MessageStorage.java | 380 ++ .../configuration/PlayerDataConfig.java | 78 + .../configuration/PlayerStorageData.java | 109 + .../configuration/TownDataConfig.java | 81 + .../configuration/TownStorageData.java | 45 + .../configuration/category/BanCategory.java | 184 + .../category/BlacklistCategory.java | 71 + .../configuration/category/ClaimCategory.java | 68 + .../category/ConfigCategory.java | 32 + .../category/CustomFlagGroupCategory.java | 72 + .../CustomFlagGroupDefinitionCategory.java | 70 + .../category/DefaultPermissionCategory.java | 205 ++ .../category/EconomyCategory.java | 41 + .../category/GeneralCategory.java | 35 + .../category/MessageCategory.java | 44 + .../category/MigratorCategory.java | 53 + .../category/ModuleCategory.java | 56 + .../category/OptionCategory.java | 64 + .../category/PlayerDataCategory.java | 58 + .../configuration/category/PvpCategory.java | 35 + .../category/ThreadCategory.java | 35 + .../configuration/category/TownCategory.java | 45 + .../category/VisualCategory.java | 94 + .../serializer/ClaimTypeSerializer.java | 50 + .../serializer/ComponentConfigSerializer.java | 86 + .../serializer/CreateModeTypeSerializer.java | 54 + .../serializer/CustomFlagSerializer.java | 193 + .../serializer/GameModeTypeSerializer.java | 58 + .../serializer/WeatherTypeSerializer.java | 54 + .../configuration/type/ConfigBase.java | 71 + .../configuration/type/GlobalConfig.java | 85 + .../economy/GDBankTransaction.java | 125 + .../event/GDAttackPlayerEvent.java | 45 + .../event/GDBorderClaimEvent.java | 125 + .../event/GDCauseStackManager.java | 97 + .../event/GDChangeClaimEvent.java | 79 + .../com/griefdefender/event/GDClaimEvent.java | 82 + .../event/GDCreateClaimEvent.java | 49 + .../event/GDFlagPermissionEvent.java | 82 + .../event/GDGroupTrustClaimEvent.java | 71 + .../griefdefender/event/GDLoadClaimEvent.java | 49 + .../griefdefender/event/GDOptionEvent.java | 73 + .../event/GDPermissionEvent.java | 91 + .../event/GDRemoveClaimEvent.java | 83 + .../griefdefender/event/GDSaveClaimEvent.java | 49 + .../griefdefender/event/GDTaxClaimEvent.java | 67 + .../event/GDTransferClaimEvent.java | 57 + .../event/GDTrustClaimEvent.java | 51 + .../event/GDUserTrustClaimEvent.java | 72 + .../com/griefdefender/event/package-info.java | 25 + .../inject/GriefDefenderImplModule.java | 62 + .../internal/EntityRemovalListener.java | 92 + .../griefdefender/internal/NbtDataHelper.java | 61 + .../internal/pagination/ActivePagination.java | 218 ++ .../pagination/GDPaginationBuilder.java | 137 + .../pagination/GDPaginationCalculator.java | 328 ++ .../pagination/GDPaginationHolder.java | 138 + .../internal/pagination/GDPaginationList.java | 145 + .../pagination/IterablePagination.java | 127 + .../internal/pagination/ListPagination.java | 107 + .../internal/pagination/PaginationList.java | 274 ++ .../pagination/TextComponentIterable.java | 49 + .../pagination/TextComponentIterator.java | 101 + .../internal/pagination/font-sizes.json | 50 + .../internal/provider/GDActor.java | 231 ++ .../internal/provider/WorldEditProvider.java | 359 ++ .../worldedit/cui/MultiSelectionColors.java | 48 + .../worldedit/cui/MultiSelectionType.java | 36 + .../cui/event/MultiSelectionClearEvent.java | 54 + .../cui/event/MultiSelectionColorEvent.java | 60 + .../cui/event/MultiSelectionCuboidEvent.java | 54 + .../cui/event/MultiSelectionGridEvent.java | 49 + .../cui/event/MultiSelectionPointEvent.java | 65 + .../registry/BlockTypeRegistryModule.java | 69 + .../registry/EntityTypeRegistryModule.java | 90 + .../internal/registry/GDBlockType.java | 50 + .../internal/registry/GDEntityType.java | 104 + .../internal/registry/GDItemType.java | 50 + .../registry/ItemTypeRegistryModule.java | 68 + .../internal/util/BlockUtil.java | 485 +++ .../griefdefender/internal/util/NMSUtil.java | 388 ++ .../internal/util/VecHelper.java | 202 ++ .../internal/visual/ClaimVisual.java | 559 +++ .../internal/visual/GDClaimVisualType.java | 155 + .../listener/BlockEventHandler.java | 989 ++++++ .../listener/CommonEntityEventHandler.java | 385 ++ .../listener/EntityEventHandler.java | 899 +++++ .../listener/LuckPermsEventHandler.java | 54 + .../listener/MCClansEventHandler.java | 79 + .../listener/NucleusEventHandler.java | 59 + .../listener/PlayerEventHandler.java | 1696 +++++++++ .../listener/WorldEventHandler.java | 97 + .../migrator/GPBukkitMigrator.java | 713 ++++ .../migrator/GPSpongeMigrator.java | 308 ++ .../migrator/RedProtectMigrator.java | 193 + .../migrator/WorldGuardMigrator.java | 793 +++++ .../permission/ContextGroupKeys.java | 36 + .../permission/ContextGroups.java | 49 + .../permission/GDPermissionGroup.java | 47 + .../permission/GDPermissionHolder.java | 72 + .../permission/GDPermissionManager.java | 1429 ++++++++ .../permission/GDPermissionResult.java | 57 + .../permission/GDPermissionUser.java | 156 + .../permission/GDPermissions.java | 250 ++ .../permission/GDResultType.java | 31 + .../permission/flag/CustomFlagData.java | 65 + .../permission/flag/FlagContexts.java | 82 + .../permission/flag/GDActiveFlagData.java | 94 + .../flag/GDCustomFlagDefinition.java | 108 + .../flag/GDCustomFlagDefinitions.java | 415 +++ .../griefdefender/permission/flag/GDFlag.java | 119 + .../permission/flag/GDFlags.java | 107 + .../permission/option/GDOption.java | 243 ++ .../permission/option/OptionBuilder.java | 69 + .../option/type/GDCreateModeType.java | 52 + .../option/type/GDGameModeType.java | 52 + .../permission/option/type/GDWeatherType.java | 52 + .../permission/ui/ClaimClickData.java | 37 + .../griefdefender/permission/ui/FlagData.java | 143 + .../griefdefender/permission/ui/MenuType.java | 35 + .../permission/ui/OptionData.java | 122 + .../griefdefender/permission/ui/UIHelper.java | 187 + .../provider/LuckPermsProvider.java | 885 +++++ .../provider/MCClansProvider.java | 41 + .../provider/NucleusProvider.java | 84 + .../provider/PermissionProvider.java | 337 ++ .../registry/ChatTypeRegistryModule.java | 91 + .../registry/ClaimTypeRegistryModule.java | 91 + .../CreateModeTypeRegistryModule.java | 91 + .../registry/FlagRegistryModule.java | 92 + .../griefdefender/registry/GDRegistry.java | 116 + .../registry/GameModeTypeRegistryModule.java | 91 + .../registry/OptionRegistryModule.java | 141 + .../registry/ResultTypeRegistryModule.java | 91 + .../registry/ShovelTypeRegistryModule.java | 91 + .../registry/TrustTypeRegistryModule.java | 91 + .../registry/WeatherTypeRegistryModule.java | 91 + .../griefdefender/storage/BaseStorage.java | 443 +++ .../griefdefender/storage/FileStorage.java | 512 +++ .../griefdefender/task/ClaimBlockTask.java | 122 + .../griefdefender/task/ClaimCleanupTask.java | 152 + .../task/ClaimVisualApplyTask.java | 86 + .../task/ClaimVisualRevertTask.java | 55 + .../griefdefender/task/PlayerTickTask.java | 101 + .../com/griefdefender/task/TaxApplyTask.java | 193 + .../text/action/GDCallbackHolder.java | 83 + .../com/griefdefender/util/BlockPosCache.java | 61 + .../com/griefdefender/util/BootstrapUtil.java | 65 + .../util/CauseContextHelper.java | 256 ++ .../griefdefender/util/ClaimClickData.java | 37 + .../com/griefdefender/util/EconomyUtil.java | 213 ++ .../com/griefdefender/util/EntityUtils.java | 72 + .../com/griefdefender/util/HttpClient.java | 107 + .../griefdefender/util/PaginationUtil.java | 93 + .../griefdefender/util/PermissionUtil.java | 242 ++ .../com/griefdefender/util/PlayerUtil.java | 207 ++ .../griefdefender/util/SpongeContexts.java | 30 + .../com/griefdefender/util/SpongeUtil.java | 106 + .../java/com/griefdefender/util/TaskUtil.java | 58 + sponge/src/main/resources/1.12.2.json | 250 ++ sponge/src/main/resources/LICENSE | 22 + .../resources/META-INF/griefdefender_at.cfg | 5 + .../src/main/resources/assets/lang/en_US.conf | 637 ++++ .../src/main/resources/assets/lang/fr_FR.conf | 637 ++++ .../src/main/resources/assets/lang/ru_RU.conf | 634 ++++ .../internal/pagination/font-sizes.json | 50 + 278 files changed, 49238 insertions(+) create mode 100644 sponge/build.gradle create mode 100644 sponge/gradle.properties create mode 100644 sponge/src/main/java/com/griefdefender/GDBootstrap.java create mode 100644 sponge/src/main/java/com/griefdefender/GDCatalogType.java create mode 100644 sponge/src/main/java/com/griefdefender/GDChatType.java create mode 100644 sponge/src/main/java/com/griefdefender/GDCore.java create mode 100644 sponge/src/main/java/com/griefdefender/GDDebugData.java create mode 100644 sponge/src/main/java/com/griefdefender/GDEventManager.java create mode 100644 sponge/src/main/java/com/griefdefender/GDPlayerData.java create mode 100644 sponge/src/main/java/com/griefdefender/GDRelocator.java create mode 100644 sponge/src/main/java/com/griefdefender/GDTimings.java create mode 100644 sponge/src/main/java/com/griefdefender/GDVersion.java create mode 100644 sponge/src/main/java/com/griefdefender/GriefDefenderPlugin.java create mode 100644 sponge/src/main/java/com/griefdefender/cache/EventResultCache.java create mode 100644 sponge/src/main/java/com/griefdefender/cache/MessageCache.java create mode 100644 sponge/src/main/java/com/griefdefender/cache/PermissionHolderCache.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/ClaimContextCalculator.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDClaim.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDClaimManager.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDClaimResult.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDClaimSchematic.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDClaimType.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDShovelType.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDTown.java create mode 100644 sponge/src/main/java/com/griefdefender/claim/GDTrustType.java create mode 100644 sponge/src/main/java/com/griefdefender/command/ClaimFlagBase.java create mode 100644 sponge/src/main/java/com/griefdefender/command/ClaimOptionBase.java create mode 100644 sponge/src/main/java/com/griefdefender/command/ClaimSubjectType.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandAdjustBonusClaimBlocks.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandCallback.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimAbandon.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonTop.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimAdmin.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimBan.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimBank.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimBasic.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimBuy.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimBuyBlocks.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimClear.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimContract.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimCreate.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimCuboid.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimDelete.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAllAdmin.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteTop.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimExpand.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFarewell.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFlag.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFlagDebug.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFlagGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFlagPlayer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimFlagReset.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimGreeting.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimIgnore.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimInfo.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimInherit.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimList.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimMode.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimName.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimOption.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimOptionGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimOptionPlayer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionPlayer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSchematic.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSell.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSellBlocks.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSetSpawn.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSpawn.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimSubdivision.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimTown.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimTransfer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimUnban.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandClaimWorldEdit.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandDebug.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandException.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandGDReload.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandGDVersion.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandGiveBlocks.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandGivePet.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandHelp.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandHelper.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandPagination.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandPlayerInfo.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandRestoreClaim.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandRestoreNature.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandSetAccruedClaimBlocks.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTownChat.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTownTag.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTrustGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTrustGroupAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTrustList.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTrustPlayer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandTrustPlayerAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandUntrustGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandUntrustGroupAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayer.java create mode 100644 sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayerAll.java create mode 100644 sponge/src/main/java/com/griefdefender/command/ComponentMessageException.java create mode 100644 sponge/src/main/java/com/griefdefender/command/gphelper/CommandAccessTrust.java create mode 100644 sponge/src/main/java/com/griefdefender/command/gphelper/CommandContainerTrust.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/ClaimDataConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/ClaimStorageData.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateStorage.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/EconomyDataConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/GriefDefenderConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/IClaimData.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/MessageDataConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/MessageStorage.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/PlayerDataConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/PlayerStorageData.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/TownDataConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/TownStorageData.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/BanCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/BlacklistCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/ClaimCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/ConfigCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupDefinitionCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/DefaultPermissionCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/EconomyCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/GeneralCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/MessageCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/MigratorCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/ModuleCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/OptionCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/PlayerDataCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/PvpCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/ThreadCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/TownCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/category/VisualCategory.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/ClaimTypeSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/ComponentConfigSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/CreateModeTypeSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/CustomFlagSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/GameModeTypeSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/serializer/WeatherTypeSerializer.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/type/ConfigBase.java create mode 100644 sponge/src/main/java/com/griefdefender/configuration/type/GlobalConfig.java create mode 100644 sponge/src/main/java/com/griefdefender/economy/GDBankTransaction.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDAttackPlayerEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDBorderClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDCauseStackManager.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDChangeClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDCreateClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDFlagPermissionEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDGroupTrustClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDLoadClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDOptionEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDPermissionEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDRemoveClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDSaveClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDTaxClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDTransferClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDTrustClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/GDUserTrustClaimEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/event/package-info.java create mode 100644 sponge/src/main/java/com/griefdefender/inject/GriefDefenderImplModule.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/EntityRemovalListener.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/NbtDataHelper.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/ActivePagination.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationBuilder.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationCalculator.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationHolder.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationList.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/IterablePagination.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/ListPagination.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/PaginationList.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterable.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterator.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/pagination/font-sizes.json create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/GDActor.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/WorldEditProvider.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionColors.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionType.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionClearEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionColorEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionCuboidEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionGridEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionPointEvent.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/BlockTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/EntityTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/GDBlockType.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/GDEntityType.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/GDItemType.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/registry/ItemTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/util/BlockUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/util/NMSUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/util/VecHelper.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/visual/ClaimVisual.java create mode 100644 sponge/src/main/java/com/griefdefender/internal/visual/GDClaimVisualType.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/BlockEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/CommonEntityEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/EntityEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/LuckPermsEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/MCClansEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/NucleusEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/PlayerEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/listener/WorldEventHandler.java create mode 100644 sponge/src/main/java/com/griefdefender/migrator/GPBukkitMigrator.java create mode 100644 sponge/src/main/java/com/griefdefender/migrator/GPSpongeMigrator.java create mode 100644 sponge/src/main/java/com/griefdefender/migrator/RedProtectMigrator.java create mode 100644 sponge/src/main/java/com/griefdefender/migrator/WorldGuardMigrator.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ContextGroupKeys.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ContextGroups.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissionGroup.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissionHolder.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissionManager.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissionResult.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissionUser.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDPermissions.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/GDResultType.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/CustomFlagData.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/FlagContexts.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/GDActiveFlagData.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinition.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinitions.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/GDFlag.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/flag/GDFlags.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/option/GDOption.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/option/OptionBuilder.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/option/type/GDCreateModeType.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/option/type/GDGameModeType.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/option/type/GDWeatherType.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ui/ClaimClickData.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ui/FlagData.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ui/MenuType.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ui/OptionData.java create mode 100644 sponge/src/main/java/com/griefdefender/permission/ui/UIHelper.java create mode 100644 sponge/src/main/java/com/griefdefender/provider/LuckPermsProvider.java create mode 100644 sponge/src/main/java/com/griefdefender/provider/MCClansProvider.java create mode 100644 sponge/src/main/java/com/griefdefender/provider/NucleusProvider.java create mode 100644 sponge/src/main/java/com/griefdefender/provider/PermissionProvider.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/ChatTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/ClaimTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/CreateModeTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/FlagRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/GDRegistry.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/GameModeTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/OptionRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/ResultTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/ShovelTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/TrustTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/registry/WeatherTypeRegistryModule.java create mode 100644 sponge/src/main/java/com/griefdefender/storage/BaseStorage.java create mode 100644 sponge/src/main/java/com/griefdefender/storage/FileStorage.java create mode 100644 sponge/src/main/java/com/griefdefender/task/ClaimBlockTask.java create mode 100644 sponge/src/main/java/com/griefdefender/task/ClaimCleanupTask.java create mode 100644 sponge/src/main/java/com/griefdefender/task/ClaimVisualApplyTask.java create mode 100644 sponge/src/main/java/com/griefdefender/task/ClaimVisualRevertTask.java create mode 100644 sponge/src/main/java/com/griefdefender/task/PlayerTickTask.java create mode 100644 sponge/src/main/java/com/griefdefender/task/TaxApplyTask.java create mode 100644 sponge/src/main/java/com/griefdefender/text/action/GDCallbackHolder.java create mode 100644 sponge/src/main/java/com/griefdefender/util/BlockPosCache.java create mode 100644 sponge/src/main/java/com/griefdefender/util/BootstrapUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/CauseContextHelper.java create mode 100644 sponge/src/main/java/com/griefdefender/util/ClaimClickData.java create mode 100644 sponge/src/main/java/com/griefdefender/util/EconomyUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/EntityUtils.java create mode 100644 sponge/src/main/java/com/griefdefender/util/HttpClient.java create mode 100644 sponge/src/main/java/com/griefdefender/util/PaginationUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/PermissionUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/PlayerUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/SpongeContexts.java create mode 100644 sponge/src/main/java/com/griefdefender/util/SpongeUtil.java create mode 100644 sponge/src/main/java/com/griefdefender/util/TaskUtil.java create mode 100644 sponge/src/main/resources/1.12.2.json create mode 100644 sponge/src/main/resources/LICENSE create mode 100644 sponge/src/main/resources/META-INF/griefdefender_at.cfg create mode 100644 sponge/src/main/resources/assets/lang/en_US.conf create mode 100644 sponge/src/main/resources/assets/lang/fr_FR.conf create mode 100644 sponge/src/main/resources/assets/lang/ru_RU.conf create mode 100644 sponge/src/main/resources/com/griefdefender/internal/pagination/font-sizes.json diff --git a/settings.gradle b/settings.gradle index 87e30e7..59c8f44 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ include "bukkit" +include "sponge" include "GriefDefenderAPI" \ No newline at end of file diff --git a/sponge/build.gradle b/sponge/build.gradle new file mode 100644 index 0000000..fe0a18a --- /dev/null +++ b/sponge/build.gradle @@ -0,0 +1,203 @@ +buildscript { + repositories { + maven { + name = 'forge' + url = 'http://files.minecraftforge.net/maven' + } + maven { + url = 'https://plugins.gradle.org/m2/' + } + } + + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0' + } +} + +plugins { + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.spongepowered.plugin' version '0.8.1' + id 'net.minecrell.vanillagradle.server' version '2.2-4' + id 'java' +} + +compileJava.options.encoding = 'UTF-8' + +// Environment variables for the build set by the build server +ext.buildNumber = System.env.BUILD_NUMBER ?: '0' + +defaultTasks 'clean', 'build' + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +archivesBaseName = project.name.toLowerCase() + +project.ext.getGitHash = { + def command = Runtime.getRuntime().exec("git rev-parse --short HEAD") + def result = command.waitFor() + return (result == 0) ? command.inputStream.text.trim() : "nogit" +} + +repositories { + mavenLocal() + maven { + name = 'sk89q' + url = 'https://maven.sk89q.com/repo' + } + maven { + name = 'bstats' + url = 'https://repo.codemc.org/repository/maven-public' + } + maven { + name = 'sponge' + url = 'https://repo.spongepowered.org/maven/' + } + maven { + name = 'nucleus' + url = 'http://repo.drnaylor.co.uk/artifactory/list/minecraft' + } + maven { + name = 'sonatype_releases' + url = 'https://oss.sonatype.org/content/repositories/releases' + } + maven { + name = 'sonatype_snapshots' + url = 'https://oss.sonatype.org/content/repositories/snapshots' + } + maven { + name = 'glare' + url = 'https://repo.glaremasters.me/repository/bloodshot' + } + maven { + name = 'aikar' + url = 'https://repo.aikar.co/content/groups/aikar' + } + maven { + name = 'worldedit' + url = 'http://maven.sk89q.com/artifactory/repo' + } + maven { + name = 'jitpack' + url = 'https://jitpack.io' + } +} + +minecraft { + version = project.minecraftVersion + mappings = project.mcpMappings +} + +sourceSets { + api +} + +dependencies { + compileOnly 'com.griefdefender:api:1.0.0-20190906.173641-10' + compileOnly "com.griefdefender:reflect-helper:1.0" + // Sponge + apiCompile "org.spongepowered:spongeapi:$apiVersion" + + compileOnly ("org.spongepowered:spongecommon:$commonVersion:dev") { + exclude module: 'testplugins' + } + // Plugins + compileOnly ("io.github.nucleuspowered:nucleus-api:1.14.1-S7.1"){ + exclude module: 'spongeapi' + } + compile "com.github.bloodmc:mcclans-api:develop-SNAPSHOT" + compileOnly "com.sk89q.worldedit:worldedit-core:6.1.4-SNAPSHOT" + + // required for bootstrap + compile "com.googlecode.json-simple:json-simple:1.1.1" + compileOnly "aopalliance:aopalliance:1.0" + compileOnly "co.aikar:acf-core:0.5.0-SNAPSHOT" + compileOnly "co.aikar:acf-sponge:0.5.0-SNAPSHOT" + compileOnly "co.aikar:locales:1.0-SNAPSHOT" + compileOnly "co.aikar:minecraft-timings:1.0.4" + compileOnly "co.aikar:Table:1.0.0-SNAPSHOT" + compileOnly "com.flowpowered:flow-math:1.0.3" + compileOnly "com.github.ben-manes.caffeine:caffeine:2.7.0" + compileOnly "com.squareup.okhttp3:okhttp:3.14.2" + compileOnly "com.squareup.okio:okio:2.2.2" + compileOnly "commons-io:commons-io:2.6" + compileOnly "it.unimi.dsi:fastutil:8.2.3" + compileOnly "javax.inject:javax.inject:1" + compileOnly "me.lucko:jar-relocator:1.3" + compileOnly "me.lucko.luckperms:luckperms-api:4.4" + compileOnly "net.jodah:expiringmap:0.5.9" + compileOnly "org.apache.commons:commons-lang3:3.9" + compileOnly "org.checkerframework:checker:2.8.2" + compileOnly "org.jetbrains:annotations:17.0.0" + compileOnly "org.jetbrains.kotlin:kotlin-stdlib:1.3.31" + compileOnly "org.ow2.asm:asm-debug-all:5.2" + compileOnly "org.spongepowered:configurate-core:3.7-SNAPSHOT" + compileOnly "org.spongepowered:configurate-gson:3.7-SNAPSHOT" + compileOnly "org.spongepowered:configurate-hocon:3.7-SNAPSHOT" + compileOnly "org.spongepowered:configurate-yaml:3.7-SNAPSHOT" + compileOnly "net.kyori:event-api:3.0.0" + compileOnly "net.kyori:event-method:3.0.0" + compileOnly "net.kyori:event-method-asm:3.0.0" + compileOnly "net.kyori:text-adapter-bukkit:3.0.3" + compileOnly "net.kyori:text-adapter-bungeecord:3.0.3" + compileOnly "net.kyori:text-adapter-spongeapi:3.0.3" + compileOnly "net.kyori:text-api:3.0.2" + compileOnly "net.kyori:text-serializer-gson:3.0.2" + compileOnly "net.kyori:text-serializer-legacy:3.0.2" + compileOnly "net.kyori:text-serializer-plain:3.0.2" +} + +jar { + manifest.attributes('FMLAT': 'griefdefender_at.cfg') + manifest.attributes('Implementation-Title': 'GriefDefender') + manifest.attributes('Implementation-Version': "$version") + manifest.attributes('Git-Hash': project.ext.getGitHash()) + classifier = 'SNAPSHOT' + baseName = 'griefdefender-sponge' +} + +if (JavaVersion.current().isJava8Compatible()) { + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + } +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives shadowJar +} + +shadowJar { + mainSpec.sourcePaths.clear() + dependsOn reobfJar + + classifier = '' + + dependencies { + include dependency("com.squareup.okhttp3:okhttp:3.9.1") + include dependency("com.squareup.okio:okio:1.13.0") + include dependency("com.googlecode.json-simple:json-simple:1.1.1") + } + + relocate("aopalliance", "com.griefdefender.lib.aopalliance") + relocate("com.github.benmanes.caffeine", "com.griefdefender.lib.caffeine") + relocate("it.unimi.dsi", "com.griefdefender.lib.fastutil") + relocate("net.jodah", "com.griefdefender.lib.jodah") + relocate("okhttp3", "com.griefdefender.lib.okhttp3") + relocate("okio", "com.griefdefender.lib.okio") + relocate("org.apache.commons.io", "com.griefdefender.lib.commonsio") + relocate("org.apache.commons.lang3", "com.griefdefender.lib.commonslang3") + relocate("org.checkerframework", "com.griefdefender.lib.checkerframework") + relocate("org.jetbrains", "com.griefdefender.lib.jetbrains") + + + exclude "dummyThing" + afterEvaluate { + from zipTree(reobfJar.jar) + } +} \ No newline at end of file diff --git a/sponge/gradle.properties b/sponge/gradle.properties new file mode 100644 index 0000000..cafd4bc --- /dev/null +++ b/sponge/gradle.properties @@ -0,0 +1,11 @@ +name=GriefDefender +group=com.griefdefender +url=https://github.com/bloodmc/GriefDefender +version=1.2.2 +apiVersion=7.2.0-SNAPSHOT +commonVersion=1.12.2-7.1.7-SNAPSHOT + +minecraftVersion=1.12.2 +mcpMappings=snapshot_20180808 + +org.gradle.jvmargs=-Xmx3G \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/GDBootstrap.java b/sponge/src/main/java/com/griefdefender/GDBootstrap.java new file mode 100644 index 0000000..a6c5b7a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDBootstrap.java @@ -0,0 +1,228 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.inject.Inject; +import com.griefdefender.util.BootstrapUtil; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.slf4j.Logger; +import org.spongepowered.api.MinecraftVersion; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.game.state.GamePreInitializationEvent; +import org.spongepowered.api.plugin.Plugin; +import org.spongepowered.api.plugin.PluginContainer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Plugin(id = "griefdefender", name = "GriefDefender", version = "1.0.0", description = "Designed to defend world from all types of grief.") +public class GDBootstrap { + + @Inject public PluginContainer pluginContainer; + @Inject private Logger logger; + @Inject @ConfigDir(sharedRoot = false) + private Path configPath; + + private Map<String, File> jarMap = new HashMap<>(); + private List<String> relocateList = new ArrayList<>(); + private static GDBootstrap instance; + private static final String LIB_ROOT_PATH = "./config/griefdefender/lib/"; + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7"; + + public static GDBootstrap getInstance() { + return instance; + } + + @Listener(order = Order.LAST) + public void onPreInit(GamePreInitializationEvent event) { + instance = this; + final JSONParser parser = new JSONParser(); + String bukkitJsonVersion = null; + this.getLogger().info("Loading libraries..."); + final MinecraftVersion version = Sponge.getPlatform().getMinecraftVersion(); + if (Sponge.getPlatform().getMinecraftVersion().getName().contains("1.12.2")) { + bukkitJsonVersion = "1.12.2"; + } else { + this.getLogger().error("Detected unsupported version '" + version.getName() + "'. GriefDefender only 1.12.2. GriefDefender will NOT load."); + return; + } + try { + final InputStream in = getClass().getResourceAsStream("/" + bukkitJsonVersion + ".json"); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + final JSONObject a = (JSONObject) parser.parse(reader); + final JSONArray libraries = (JSONArray) a.get("libraries"); + if (libraries == null) { + this.getLogger().error("Resource " + bukkitJsonVersion + ".json is corrupted!. Please contact author for assistance."); + return; + } + final Iterator<JSONObject> iterator = libraries.iterator(); + while (iterator.hasNext()) { + JSONObject lib = iterator.next(); + final String name = (String) lib.get("name"); + final String sha1 = (String) lib.get("sha1"); + final String path = (String) lib.get("path"); + final String relocate = (String) lib.get("relocate"); + final String url = (String) lib.get("url"); + final Path libPath = Paths.get(LIB_ROOT_PATH).resolve(path); + final File file = libPath.toFile(); + downloadLibrary(name, relocate, sha1, url, libPath); + } + } catch (Throwable t) { + t.printStackTrace(); + } + // Inject jar-relocator and asm debug + injectRelocatorDeps(); + // Relocate all GD dependencies and inject + GDRelocator.getInstance().relocateJars(this.jarMap); + // Boot GD + GriefDefenderPlugin.getInstance().onPreInit(event, this.logger, this.configPath, this.pluginContainer); + //Sponge.getEventManager().registerListeners(GriefDefenderPlugin.getInstance(), GriefDefenderPlugin.getInstance()); + } + + public List<String> getRelocateList() { + return this.relocateList; + } + + private void injectRelocatorDeps() { + String name = "org.ow2.asm:asm-debug-all:5.2"; + File file = this.jarMap.get(name); + BootstrapUtil.addUrlToClassLoader(name, file); + name = "me.lucko:jar-relocator:1.3"; + file = this.jarMap.get(name); + BootstrapUtil.addUrlToClassLoader(name, file); + // inject reflect helper + final String javaVersion = System.getProperty("java.version"); + if (getJavaVersion() >= 11) { + name = "com.griefdefender:reflect-helper:2.0"; + } else { + name = "com.griefdefender:reflect-helper:1.0"; + } + file = this.jarMap.get(name); + BootstrapUtil.addUrlToClassLoader(name, file); + } + + public void downloadLibrary(String name, String relocate, String sha1, String url, Path libPath) { + final File file = libPath.toFile(); + this.jarMap.put(name, file); + if (relocate != null && !relocate.isEmpty() && relocate.contains(":")) { + this.relocateList.add(relocate); + } + if (!Files.exists(libPath)) { + this.getLogger().info("Downloading library " + name + " ..."); + try { + URL website = new URL(url); + URLConnection urlConnection = website.openConnection(); + // Some maven repos like nexus require a user agent so we just pass one to satisfy it + urlConnection.setRequestProperty("User-Agent", USER_AGENT); + ReadableByteChannel rbc = Channels.newChannel(urlConnection.getInputStream()); + if (!Files.exists(libPath)) { + file.getParentFile().mkdirs(); + } + FileOutputStream fos = new FileOutputStream(file); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + } catch (IOException e) { + this.getLogger().error("An error occured while downloading library '" + name + "'. Skipping..."); + e.printStackTrace(); + return; + } + + + final String hash = getLibraryHash(file); + + if (hash == null || !sha1.equals(hash)) { + this.getLogger().error("Detected invalid hash '" + hash + "' for file '" + libPath + "'. Expected '" + sha1 + "'. Skipping..."); + try { + Files.delete(libPath); + return; + } catch (IOException e) { + e.printStackTrace(); + return; + } + } + } + + this.jarMap.put(name, file); + } + + private String getLibraryHash(File file) { + try { + final MessageDigest md = MessageDigest.getInstance("SHA-1"); + final byte[] data = Files.readAllBytes(file.toPath()); + final byte[] b = md.digest(data); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < b.length; i++) { + if ((0xff & b[i]) < 0x10) { + buffer.append("0" + Integer.toHexString((0xFF & b[i]))); + } else { + buffer.append(Integer.toHexString(0xFF & b[i])); + } + } + return buffer.toString(); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + public Logger getLogger() { + return this.logger; + } + + private static int getJavaVersion() { + String version = System.getProperty("java.version"); + if(version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + final int dot = version.indexOf("."); + if(dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } +} diff --git a/sponge/src/main/java/com/griefdefender/GDCatalogType.java b/sponge/src/main/java/com/griefdefender/GDCatalogType.java new file mode 100644 index 0000000..b106f3b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDCatalogType.java @@ -0,0 +1,76 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.CatalogType; + +import java.util.StringJoiner; + +public abstract class GDCatalogType implements CatalogType { + + private final String id; + + public GDCatalogType(String id) { + this.id = checkNotNull(id, "id"); + } + + @Override + public final String getId() { + return this.id; + } + + @Override + public String getName() { + return getId(); + } + + @Override + public final int hashCode() { + return this.id.hashCode(); + } + + @Override + public final boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final CatalogType other = (CatalogType) obj; + return getId().equals(other.getId()); + } + + @Override + public String toString() { + return new StringJoiner(", ", GDCatalogType.class.getSimpleName() + "[", "]") + .add("id=" + getId()) + .add("name=" + getName()) + .toString(); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/GDChatType.java b/sponge/src/main/java/com/griefdefender/GDChatType.java new file mode 100644 index 0000000..7e4444b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDChatType.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.griefdefender.api.ChatType; + +public class GDChatType implements ChatType { + + private final String id; + private final String name; + + public GDChatType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/GDCore.java b/sponge/src/main/java/com/griefdefender/GDCore.java new file mode 100644 index 0000000..722c2b6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDCore.java @@ -0,0 +1,109 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Singleton; +import com.griefdefender.api.Core; +import com.griefdefender.api.Group; +import com.griefdefender.api.Subject; +import com.griefdefender.api.User; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimManager; +import com.griefdefender.api.data.PlayerData; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.cache.PermissionHolderCache; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.world.World; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Singleton +public class GDCore implements Core { + + @Override + public boolean isEnabled(UUID worldUniqueId) { + return GriefDefenderPlugin.getInstance().claimsEnabledForWorld(worldUniqueId); + } + + @Override + public ClaimBlockSystem getClaimBlockSystem() { + return GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.claimBlockSystem; + } + + @Override + public boolean isEconomyModeEnabled() { + return GriefDefenderPlugin.getInstance().isEconomyModeEnabled(); + } + + @Override + public boolean isProtectionModuleEnabled(Flag flag) { + return GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(flag.toString()); + } + + @Override + public ClaimManager getClaimManager(UUID worldUniqueId) { + return GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(worldUniqueId); + } + + @Override + public Optional<PlayerData> getPlayerData(UUID worldUniqueId, UUID playerUniqueId) { + return Optional.ofNullable(GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(worldUniqueId, playerUniqueId)); + } + + @Override + public List<Claim> getAllPlayerClaims(UUID playerUniqueId) { + List<Claim> claimList = new ArrayList<>(); + for (World world : Sponge.getServer().getWorlds()) { + claimList.addAll(this.getClaimManager(world.getUniqueId()).getPlayerClaims(playerUniqueId)); + } + + return ImmutableList.copyOf(claimList); + } + + @Override + public Subject getDefaultSubject() { + return GriefDefenderPlugin.DEFAULT_HOLDER; + } + + @Override + public Subject getSubject(String identifier) { + return PermissionHolderCache.getInstance().getOrCreateHolder(identifier); + } + + @Override + public User getUser(UUID uuid) { + return PermissionHolderCache.getInstance().getOrCreateUser(uuid); + } + + @Override + public Group getGroup(String name) { + return PermissionHolderCache.getInstance().getOrCreateGroup(name); + } +} diff --git a/sponge/src/main/java/com/griefdefender/GDDebugData.java b/sponge/src/main/java/com/griefdefender/GDDebugData.java new file mode 100644 index 0000000..41eca12 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDDebugData.java @@ -0,0 +1,224 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.griefdefender.api.Tristate; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.util.HttpClient; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.plugin.PluginContainer; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.zip.GZIPOutputStream; + +public class GDDebugData { + private static final String BYTEBIN_ENDPOINT = "https://bytebin.lucko.me/post"; + private static final String DEBUG_VIEWER_URL = "https://griefdefender.github.io/debug/?"; + private static final MediaType PLAIN_TYPE = MediaType.parse("text/plain; charset=utf-8"); + + private static final int MAX_LINES = 5000; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + private static final Component GD_TEXT = TextComponent.builder("").append("[", TextColor.WHITE).append("GD", TextColor.AQUA).append("] ", TextColor.WHITE).build(); + + private final CommandSource source; + private final List<String> header; + private final List<String> records; + private final long startTime = System.currentTimeMillis(); + private boolean verbose; + private User target; + + public GDDebugData(CommandSource source, User target, boolean verbose) { + this.source = source; + this.target = target; + this.verbose = verbose; + this.records = new ArrayList<>(); + this.header = new ArrayList<>(); + this.header.add("# GriefDefender Debug Log"); + this.header.add("#### This file was automatically generated by [GriefDefender](https://github.com/bloodmc/GriefDefender) "); + this.header.add(""); + this.header.add("### Metadata"); + this.header.add("| Key | Value |"); + this.header.add("|-----|-------|"); + this.header.add("| GD Version | " + GriefDefenderPlugin.IMPLEMENTATION_VERSION + "|"); + this.header.add("| Sponge Version | " + GriefDefenderPlugin.SPONGE_VERSION + "|"); + final PluginContainer lpContainer = Sponge.getPluginManager().getPlugin("luckperms").orElse(null); + if (lpContainer != null) { + final String version = lpContainer.getVersion().orElse(null); + if (version != null) { + this.header.add("| LuckPerms Version | " + version); + } + } + this.header.add("| " + PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_USER) + " | " + (this.target == null ? "ALL" : this.target.getName()) + "|"); + this.header.add("| " + PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().DEBUG_RECORD_START) + " | " + DATE_FORMAT.format(new Date(this.startTime)) + "|"); + } + + public void addRecord(String flag, String trust, String source, String target, String location, String user, String permission, Tristate result) { + if (this.records.size() < MAX_LINES) { + this.records.add("| " + flag + " | " + trust + " | " + source + " | " + target + " | " + location + " | " + user + " | " + permission + " | " + result + " | "); + } else { + TextAdapter.sendComponent(this.source, TextComponent.builder("").append("MAX DEBUG LIMIT REACHED!").append("\n") + .append("Pasting output...", TextColor.GREEN).build()); + this.pasteRecords(); + this.records.clear(); + GriefDefenderPlugin.debugActive = false; + TextAdapter.sendComponent(this.source, TextComponent.builder("").append(GD_TEXT).append("Debug ", TextColor.GRAY).append("OFF", TextColor.RED).build()); + } + } + + public CommandSource getSource() { + return this.source; + } + + public User getTarget() { + return this.target; + } + + public boolean isRecording() { + return !this.verbose; + } + + public void setTarget(User user) { + this.target = user; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void pasteRecords() { + if (this.records.isEmpty()) { + TextAdapter.sendComponent(this.source, MessageCache.getInstance().DEBUG_NO_RECORDS); + return; + } + + final long endTime = System.currentTimeMillis(); + List<String> debugOutput = new ArrayList<>(this.header); + final String RECORD_END = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().DEBUG_RECORD_END); + final String TIME_ELAPSED = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().DEBUG_TIME_ELAPSED); + final String OUTPUT = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_OUTPUT); + final String FLAG = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_FLAG); + final String TRUST = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_TRUST); + final String LOCATION = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_LOCATION); + final String SOURCE = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_SOURCE); + final String TARGET = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_TARGET); + final String USER = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_USER); + final String PERMISSION = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_PERMISSION); + final String RESULT = PlainComponentSerializer.INSTANCE.serialize(MessageCache.getInstance().LABEL_RESULT); + debugOutput.add("| " + RECORD_END + " | " + DATE_FORMAT.format(new Date(endTime)) + "|"); + long elapsed = (endTime - startTime) / 1000L; + debugOutput.add("| " + TIME_ELAPSED + " | " + elapsed + " seconds" + "|"); + debugOutput.add(""); + debugOutput.add("### " + OUTPUT) ; + debugOutput.add("| " + FLAG + " | " + TRUST + " | " + SOURCE + " | " + TARGET + " | " + LOCATION + " | " + USER + " | " + PERMISSION + " | " + RESULT + " |"); + debugOutput.add("|------|-------|--------|--------|----------|------|------------|--------|"); + + debugOutput.addAll(this.records); + + String content = String.join("\n", debugOutput); + + String pasteId; + try { + pasteId = postContent(content); + } catch (Exception e) { + TextAdapter.sendComponent(this.source, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DEBUG_ERROR_UPLOAD, + ImmutableMap.of("content", TextComponent.of(e.getMessage(), TextColor.WHITE)))); + return; + } + + String url = DEBUG_VIEWER_URL + pasteId; + + URL jUrl; + try { + jUrl = new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + TextAdapter.sendComponent(this.source, TextComponent.builder() + .append(MessageCache.getInstance().DEBUG_PASTE_SUCCESS) + .append(" : " + url, TextColor.GREEN) + .clickEvent(ClickEvent.openUrl(jUrl.toString())).build()); + } + + private static String postContent(String content) throws IOException { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + try (GZIPOutputStream writer = new GZIPOutputStream(byteOut)) { + writer.write(content.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + RequestBody body = RequestBody.create(PLAIN_TYPE, byteOut.toByteArray()); + + Request.Builder requestBuilder = new Request.Builder() + .url(BYTEBIN_ENDPOINT) + .header("Content-Encoding", "gzip") + .post(body); + + Request request = requestBuilder.build(); + try (Response response = HttpClient.makeCall(request)) { + try (ResponseBody responseBody = response.body()) { + if (responseBody == null) { + throw new RuntimeException("No response"); + } + + try (InputStream inputStream = responseBody.byteStream()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + JsonObject object = new Gson().fromJson(reader, JsonObject.class); + return object.get("key").getAsString(); + } + } + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/GDEventManager.java b/sponge/src/main/java/com/griefdefender/GDEventManager.java new file mode 100644 index 0000000..5bd326c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDEventManager.java @@ -0,0 +1,67 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.inject.Singleton; +import com.griefdefender.api.event.Event; +import com.griefdefender.api.event.EventManager; +import net.kyori.event.EventBus; +import net.kyori.event.SimpleEventBus; +import net.kyori.event.method.MethodSubscriptionAdapter; +import net.kyori.event.method.SimpleMethodSubscriptionAdapter; +import net.kyori.event.method.asm.ASMEventExecutorFactory; + +@Singleton +public class GDEventManager implements EventManager { + + private final EventBus<Event> bus; + private final MethodSubscriptionAdapter<Object> adapter; + + public GDEventManager() { + this.bus = new SimpleEventBus<Event>(Event.class); + this.adapter = new SimpleMethodSubscriptionAdapter<>(bus, new ASMEventExecutorFactory<>()); + } + + @Override + public EventBus<Event> getBus() { + return this.bus; + } + + @Override + public void post(Event event) { + this.bus.post(event); + } + + @Override + public void register(Object listener) { + this.adapter.register(listener); + } + + @Override + public void unregister(Object listener) { + this.adapter.unregister(listener); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/GDPlayerData.java b/sponge/src/main/java/com/griefdefender/GDPlayerData.java new file mode 100644 index 0000000..d060048 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDPlayerData.java @@ -0,0 +1,701 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ShovelType; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.api.data.PlayerData; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.EventResultCache; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.PlayerStorageData; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.data.Transaction; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.lang.ref.WeakReference; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +public class GDPlayerData implements PlayerData { + + public UUID playerID; + public UUID worldUniqueId; + private WeakReference<GDPermissionUser> playerSubject; + private Set<Claim> claimList; + private PlayerStorageData playerStorage; + public Location<World> lastAfkCheckLocation; + public Location<World> lastShovelLocation; + public Location<World> endShovelLocation; + public Location<World> lastValidInspectLocation; + public boolean claimMode = false; + public ShovelType shovelMode = ShovelTypes.BASIC; + + public GDClaim claimResizing; + public GDClaim claimSubdividing; + + public List<Transaction<BlockSnapshot>> visualBlocks = new ArrayList<>(); + public UUID visualClaimId; + public UUID petRecipientUniqueId; + public Task visualRevertTask; + + public boolean ignoreClaims = false; + + public boolean debugClaimPermissions = false; + public WeakReference<GDClaim> lastClaim = new WeakReference<>(null); + + public boolean inTown = false; + public boolean townChat = false; + + // Always ignore active contexts by default + // This prevents protection issues when other plugins call getActiveContext + public boolean ignoreActiveContexts = true; + + public EventResultCache eventResultCache; + + // collide event cache + public int lastCollideEntityId = 0; + public boolean lastCollideEntityResult = false; + + private String playerName; + + public boolean allowFlight = false; + public boolean ignoreFallDamage = false; + + // teleport data + public int teleportDelay = 0; + public Location<World> teleportSourceLocation; + public Location<World> teleportLocation; + + public Instant lastPvpTimestamp; + + // cached global option values + public int minClaimLevel; + private CreateModeType optionClaimCreateMode; + private Integer optionMaxAccruedBlocks; + + // cached permission values + public boolean canManageAdminClaims = false; + public boolean canManageWilderness = false; + public boolean canManageGlobalOptions = false; + public boolean canManageAdminOptions = false; + public boolean canManageOverrideOptions = false; + public boolean canManageFlagDefaults = false; + public boolean canManageFlagOverrides = false; + public boolean bypassBorderCheck = false; + public boolean ignoreAdminClaims = false; + public boolean ignoreBasicClaims = false; + public boolean ignoreTowns = false; + public boolean ignoreWilderness = false; + + public boolean dataInitialized = false; + public boolean showVisualFillers = true; + public boolean useRestoreSchematic = false; + private boolean checkedDimensionHeight = false; + + public GDPlayerData(UUID worldUniqueId, UUID playerUniqueId, PlayerStorageData playerStorage, GriefDefenderConfig<?> activeConfig, Set<Claim> claims) { + this.worldUniqueId = worldUniqueId; + this.playerID = playerUniqueId; + this.playerStorage = playerStorage; + this.claimList = claims; + this.refreshPlayerOptions(); + } + + public void refreshPlayerOptions() { + final GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(this.worldUniqueId); + GriefDefenderPlugin.getInstance().executor.execute(() -> { + if (this.playerSubject == null || this.playerSubject.get() == null) { + GDPermissionUser subject = PermissionHolderCache.getInstance().getOrCreateUser(this.playerID); + this.playerSubject = new WeakReference<>(subject); + } + final GDPermissionUser subject = this.playerSubject.get(); + final Set<Context> activeContexts = new HashSet<>(); + PermissionUtil.getInstance().addActiveContexts(activeContexts, subject); + // permissions + this.bypassBorderCheck = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.BYPASS_BORDER_CHECK, activeContexts).asBoolean(); + this.ignoreAdminClaims = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.IGNORE_CLAIMS_ADMIN, activeContexts).asBoolean(); + this.ignoreTowns = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.IGNORE_CLAIMS_TOWN, activeContexts).asBoolean(); + this.ignoreWilderness = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.IGNORE_CLAIMS_WILDERNESS, activeContexts).asBoolean(); + this.ignoreBasicClaims = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.IGNORE_CLAIMS_BASIC, activeContexts).asBoolean(); + this.canManageAdminClaims = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.COMMAND_ADMIN_CLAIMS, activeContexts).asBoolean(); + this.canManageWilderness = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_WILDERNESS, activeContexts).asBoolean(); + this.canManageOverrideOptions = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_OVERRIDE_OPTIONS, activeContexts).asBoolean(); + this.canManageGlobalOptions = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_GLOBAL_OPTIONS, activeContexts).asBoolean(); + this.canManageAdminOptions = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_ADMIN_OPTIONS, activeContexts).asBoolean(); + this.canManageFlagDefaults = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_FLAG_DEFAULTS, activeContexts).asBoolean(); + this.canManageFlagOverrides = PermissionUtil.getInstance().getPermissionValue(subject, GDPermissions.MANAGE_FLAG_OVERRIDES, activeContexts).asBoolean(); + this.playerID = subject.getUniqueId(); + /*if (this.optionMaxClaimLevel > 255 || this.optionMaxClaimLevel <= 0 || this.optionMaxClaimLevel < this.optionMinClaimLevel) { + this.optionMaxClaimLevel = 255; + } + if (this.optionMinClaimLevel < 0 || this.optionMinClaimLevel >= 255 || this.optionMinClaimLevel > this.optionMaxClaimLevel) { + this.optionMinClaimLevel = 0; + }*/ + this.dataInitialized = true; + this.checkedDimensionHeight = false; + }); + } + + public String getPlayerName() { + if (this.playerName == null) { + GDPermissionUser user = this.playerSubject.get(); + if (user == null) { + user = PermissionHolderCache.getInstance().getOrCreateUser(this.playerID); + } + if (user != null) { + this.playerName = user.getFriendlyName(); + } + if (this.playerName == null) { + this.playerName = "[unknown]"; + } + } + + return this.playerName; + } + + public void revertActiveVisual(Player player) { + if (this.visualRevertTask != null) { + this.visualRevertTask.cancel(); + this.visualRevertTask = null; + } + + this.lastShovelLocation = null; + GDClaim claim = null; + if (this.visualClaimId != null) { + claim = (GDClaim) GriefDefenderPlugin.getInstance().dataStore.getClaim(this.worldUniqueId, this.visualClaimId); + if (claim != null) { + claim.playersWatching.remove(this.playerID); + } + } + this.visualClaimId = null; + if (this.visualBlocks.isEmpty()|| !player.getWorld().equals(this.visualBlocks.get(0).getFinal().getLocation().get().getExtent())) { + return; + } + + for (int i = 0; i < this.visualBlocks.size(); i++) { + BlockSnapshot snapshot = this.visualBlocks.get(i).getOriginal(); + // If original block does not exist, do not send to player + if (snapshot.getState().getType() != snapshot.getLocation().get().getBlockType()) { + if (claim != null) { + claim.markVisualDirty = true; + } + continue; + } + player.sendBlockChange(snapshot.getPosition(), snapshot.getState()); + } + this.visualBlocks.clear(); + } + + @Override + public int getBlocksAccruedPerHour() { + final Integer value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.BLOCKS_ACCRUED_PER_HOUR); + if (value == null) { + return Options.BLOCKS_ACCRUED_PER_HOUR.getDefaultValue(); + } + return value; + } + + @Override + public int getChestClaimExpiration() { + final Integer value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.CHEST_EXPIRATION); + if (value == null) { + return Options.CHEST_EXPIRATION.getDefaultValue(); + } + return value; + } + + @Override + public int getCreateClaimLimit(ClaimType type) { + final Integer value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.CREATE_LIMIT, type); + if (value == null) { + return Options.CREATE_LIMIT.getDefaultValue(); + } + return value; + } + + public CreateModeType getCreateMode() { + final CreateModeType value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(CreateModeType.class), this.getSubject(), Options.CREATE_MODE); + if (value == null || value == CreateModeTypes.UNDEFINED) { + return CreateModeTypes.AREA; + } + return value; + } + + @Override + public int getInitialClaimBlocks() { + final Integer value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.INITIAL_BLOCKS); + if (value == null) { + return Options.INITIAL_BLOCKS.getDefaultValue(); + } + return value; + } + + public double getInternalEconomyBlockCost() { + final Double value = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.ECONOMY_BLOCK_COST); + if (value == null) { + return Options.ECONOMY_BLOCK_COST.getDefaultValue(); + } + return value; + } + + public int getInternalRemainingClaimBlocks() { + final int initialClaimBlocks = this.getInitialClaimBlocks(); + int remainingBlocks = initialClaimBlocks + this.getAccruedClaimBlocks() + this.getBonusClaimBlocks(); + + for (Claim claim : this.claimList) { + if (claim.isSubdivision()) { + continue; + } + + GDClaim gpClaim = (GDClaim) claim; + if ((gpClaim.parent == null || gpClaim.parent.isAdminClaim()) && claim.getData().requiresClaimBlocks()) { + remainingBlocks -= claim.getClaimBlocks(); + } + } + + return remainingBlocks; + } + + @Override + public int getRemainingClaimBlocks() { + final int initialClaimBlocks = this.getInitialClaimBlocks(); + int remainingBlocks = initialClaimBlocks + this.getAccruedClaimBlocks() + this.getBonusClaimBlocks(); + + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + return this.getInternalEconomyAvailablePurchaseCost(); + } else { + for (Claim claim : this.claimList) { + if (claim.isSubdivision()) { + continue; + } + + GDClaim gpClaim = (GDClaim) claim; + if ((gpClaim.parent == null || gpClaim.parent.isAdminClaim()) && claim.getData().requiresClaimBlocks()) { + remainingBlocks -= claim.getClaimBlocks(); + } + } + } + + return remainingBlocks; + } + + public int getInternalEconomyAvailablePurchaseCost() { + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(this.playerID).orElse(null); + if (playerAccount == null) { + return 0; + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + final Double economyBlockCost = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.ECONOMY_BLOCK_COST); + return (int) Math.round((currentFunds.doubleValue() / economyBlockCost)); + } + return 0; + } + + public int getTotalClaimsCost() { + int totalCost = 0; + for (Claim claim : this.claimList) { + if (claim.isSubdivision()) { + continue; + } + + final GDClaim gpClaim = (GDClaim) claim; + if ((gpClaim.parent == null || gpClaim.parent.isAdminClaim()) && claim.getData().requiresClaimBlocks()) { + totalCost += claim.getClaimBlocks(); + } + } + + return totalCost; + } + + public double getRemainingChunks() { + final double remainingChunks = this.getRemainingClaimBlocks() / 65536.0; + return Math.round(remainingChunks * 100.0)/100.0; + } + + @Override + public int getAccruedClaimBlocks() { + return this.playerStorage.getConfig().getAccruedClaimBlocks(); + } + + public boolean addAccruedClaimBlocks(int newAccruedClaimBlocks) { + int currentTotal = this.getAccruedClaimBlocks(); + if ((currentTotal + newAccruedClaimBlocks) > this.getMaxAccruedClaimBlocks()) { + return false; + } + + this.playerStorage.getConfig().setAccruedClaimBlocks(currentTotal + newAccruedClaimBlocks); + return true; + } + + public boolean setAccruedClaimBlocks(int newAccruedClaimBlocks) { + if (newAccruedClaimBlocks > this.getMaxAccruedClaimBlocks()) { + return false; + } + + this.playerStorage.getConfig().setAccruedClaimBlocks(newAccruedClaimBlocks); + return true; + } + + public int getBonusClaimBlocks() { + return this.playerStorage.getConfig().getBonusClaimBlocks(); + } + + public void setBonusClaimBlocks(int bonusClaimBlocks) { + this.playerStorage.getConfig().setBonusClaimBlocks(bonusClaimBlocks); + } + + public CreateModeType getClaimCreateMode() { + if (this.optionClaimCreateMode == null) { + CreateModeType mode = this.getCreateMode(); + // default to 0 if invalid + if (mode == null) { + mode = CreateModeTypes.AREA; + } + this.optionClaimCreateMode = mode; + } + + return this.optionClaimCreateMode; + } + + public void setClaimCreateMode(CreateModeType mode) { + this.optionClaimCreateMode = mode; + } + + public boolean canCreateClaim(Player player) { + return canCreateClaim(player, false); + } + + public boolean canCreateClaim(Player player, boolean sendMessage) { + final CreateModeType createMode = this.getClaimCreateMode(); + if (this.shovelMode == ShovelTypes.BASIC) { + if (createMode == CreateModeTypes.AREA && !player.hasPermission(GDPermissions.CLAIM_CREATE_BASIC)) { + if (sendMessage) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_CREATE); + } + return false; + } + if (createMode == CreateModeTypes.VOLUME && !player.hasPermission(GDPermissions.CLAIM_CUBOID_BASIC)) { + if (sendMessage) { + GriefDefenderPlugin.sendMessage(player,MessageCache.getInstance().PERMISSION_CUBOID); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CUBOID_DISABLED); + } + return false; + } + } else if (this.shovelMode == ShovelTypes.SUBDIVISION) { + if (createMode == CreateModeTypes.AREA && !player.hasPermission(GDPermissions.CLAIM_CREATE_SUBDIVISION)) { + if (sendMessage) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_CREATE); + } + return false; + } else if (!player.hasPermission(GDPermissions.CLAIM_CUBOID_SUBDIVISION)) { + if (sendMessage) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CUBOID); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CUBOID_DISABLED); + } + return false; + } + } else if (this.shovelMode == ShovelTypes.ADMIN) { + if (createMode == CreateModeTypes.AREA && !player.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS)) { + return false; + } else if (!player.hasPermission(GDPermissions.CLAIM_CUBOID_ADMIN)) { + return false; + } + } else if (this.shovelMode == ShovelTypes.TOWN) { + if (createMode == CreateModeTypes.AREA && !player.hasPermission(GDPermissions.CLAIM_CREATE_TOWN)) { + return false; + } else if (!player.hasPermission(GDPermissions.CLAIM_CUBOID_TOWN)) { + return false; + } + } + + return true; + } + + public void saveAllData() { + this.playerStorage.save(); + } + + public PlayerStorageData getStorageData() { + return this.playerStorage; + } + + public Set<Claim> getClaims() { + return ImmutableSet.copyOf(this.claimList); + } + + public Set<Claim> getInternalClaims() { + return this.claimList; + } + + public int getClaimTypeCount(ClaimType type) { + int count = 0; + for (Claim claim : this.claimList) { + if (claim.getType() == type) { + count++; + } + } + return count; + } + + public void setLastCollideEntityData(int entityId, boolean result) { + this.lastCollideEntityId = entityId; + this.lastCollideEntityResult = result; + } + + public void setIgnoreClaims(boolean flag) { + this.ignoreClaims = flag; + } + + @Override + public boolean canIgnoreClaim(Claim claim) { + if (claim == null || this.ignoreClaims == false) { + return false; + } + + if (claim.isAdminClaim()) { + return this.ignoreAdminClaims; + } else if (claim.isWilderness()) { + return this.ignoreWilderness; + } else if (claim.isTown()) { + return this.ignoreTowns; + } + return this.ignoreBasicClaims; + } + + public boolean canManageOption(Player player, GDClaim claim, boolean isGroup) { + if (claim.allowEdit(player) != null) { + return false; + } + + if (claim.isWilderness()) { + return player.hasPermission(GDPermissions.MANAGE_WILDERNESS); + } + if (isGroup) { + if (claim.isTown() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_GROUP_TOWN)) { + return true; + } + if (claim.isAdminClaim() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_GROUP_ADMIN)) { + return true; + } + if (claim.isBasicClaim() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_GROUP_BASIC)) { + return true; + } + if (claim.isSubdivision() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_GROUP_SUBDIVISION)) { + return true; + } + } else { + if (claim.isTown() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_PLAYER_TOWN)) { + return true; + } + if (claim.isAdminClaim() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_PLAYER_ADMIN)) { + return true; + } + if (claim.isBasicClaim() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_PLAYER_BASIC)) { + return true; + } + if (claim.isSubdivision() && player.hasPermission(GDPermissions.COMMAND_CLAIM_OPTIONS_PLAYER_SUBDIVISION)) { + return true; + } + } + + return false; + } + + @Override + public int getMaxAccruedClaimBlocks() { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MAX_ACCRUED_BLOCKS); + } + + @Override + public double getAbandonedReturnRatio(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.ABANDON_RETURN_RATIO); + } + + @Override + public int getMaxClaimX(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MAX_SIZE_X, type); + } + + @Override + public int getMaxClaimY(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MAX_SIZE_Y, type); + } + + @Override + public int getMaxClaimZ(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MAX_SIZE_Z, type); + } + + @Override + public int getMinClaimX(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MIN_SIZE_X, type); + } + + @Override + public int getMinClaimY(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MIN_SIZE_Y, type); + } + + @Override + public int getMinClaimZ(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MIN_SIZE_Z, type); + } + + @Override + public int getMaxClaimLevel() { + int maxClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MAX_LEVEL); + if (!this.checkedDimensionHeight) { + final World world = Sponge.getServer().getWorld(this.worldUniqueId).orElse(null); + if (world != null) { + final int buildHeight = world.getDimension().getBuildHeight() - 1; + if (buildHeight < maxClaimLevel) { + maxClaimLevel = buildHeight; + } + } + this.checkedDimensionHeight = true; + } + return maxClaimLevel; + } + + @Override + public int getMinClaimLevel() { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), this.getSubject(), Options.MIN_LEVEL); + } + + @Override + public double getEconomyClaimBlockCost() { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.ECONOMY_BLOCK_COST); + } + + @Override + public double getEconomyClaimBlockReturn() { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.ECONOMY_BLOCK_SELL_RETURN); + } + + @Override + public double getTaxRate(ClaimType type) { + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), this.getSubject(), Options.TAX_RATE, type); + } + + @Override + public String getSubjectId() { + return this.getSubject().getIdentifier(); + } + + public GDPermissionUser getSubject() { + this.playerSubject = null; + if (this.playerSubject == null || this.playerSubject.get() == null) { + GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(this.playerID); + this.playerSubject = new WeakReference<>(user); + } + + return this.playerSubject.get(); + } + + public void sendTaxExpireMessage(Player player, GDClaim claim) { + final double taxRate = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), player, Options.TAX_RATE, claim); + final double taxOwed = claim.getClaimBlocks() * taxRate; + final double remainingDays = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.TAX_EXPIRATION_DAYS_KEEP, claim); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TAX_CLAIM_EXPIRED, ImmutableMap.of( + "days", remainingDays, + "amount", taxOwed)); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } + + public double getTotalTax() { + double totalTax = 0; + final GDPermissionUser subject = this.getSubject(); + for (Claim claim : this.getInternalClaims()) { + double playerTaxRate = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), subject, Options.TAX_RATE, claim); + totalTax += (claim.getClaimBlocks() / 256) * playerTaxRate; + } + + return totalTax; + } + + public boolean inPvpCombat(World world) { + if (this.lastPvpTimestamp == null) { + return false; + } + + final Instant now = Instant.now(); + final int combatTimeout = GriefDefenderPlugin.getActiveConfig(world.getProperties()).getConfig().pvp.combatTimeout; + if (this.lastPvpTimestamp.plusSeconds(combatTimeout).isBefore(now)) { + this.lastPvpTimestamp = null; + return false; + } + + return true; + } + + public void onDisconnect() { + this.visualBlocks.clear(); + this.claimMode = false; + this.lastShovelLocation = null; + this.eventResultCache = null; + this.claimResizing = null; + this.claimSubdividing = null; + this.visualClaimId = null; + if (this.visualRevertTask != null) { + this.visualRevertTask.cancel(); + this.visualRevertTask = null; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/GDRelocator.java b/sponge/src/main/java/com/griefdefender/GDRelocator.java new file mode 100644 index 0000000..5807f0f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDRelocator.java @@ -0,0 +1,78 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.griefdefender.util.BootstrapUtil; +import me.lucko.jarrelocator.JarRelocator; +import me.lucko.jarrelocator.Relocation; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class GDRelocator { + + private static GDRelocator instance; + private List<Relocation> rules; + + public static GDRelocator getInstance() { + if (instance == null) { + instance = new GDRelocator(); + } + return instance; + } + + public GDRelocator() { + this.rules = new ArrayList<>(); + for (String name : GDBootstrap.getInstance().getRelocateList()) { + final String[] parts = name.split(":"); + final String key = parts[0]; + final String relocated = parts[1]; + this.rules.add(new Relocation(key, "com.griefdefender.lib." + relocated)); + } + } + + public void relocateJars(Map<String, File> jarMap) { + for (Map.Entry<String, File> mapEntry : jarMap.entrySet()) { + final String name = mapEntry.getKey(); + final File input = mapEntry.getValue(); + final File output = Paths.get(input.getParentFile().getPath()).resolve(input.getName().replace(".jar", "") + "-shaded.jar").toFile(); + if (!output.exists()) { + // Relocate + JarRelocator relocator = new JarRelocator(input, output, this.rules); + + try { + relocator.run(); + } catch (IOException e) { + throw new RuntimeException("Unable to relocate", e); + } + } + BootstrapUtil.addUrlToClassLoader(name, output); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/GDTimings.java b/sponge/src/main/java/com/griefdefender/GDTimings.java new file mode 100644 index 0000000..519923a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDTimings.java @@ -0,0 +1,81 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import co.aikar.timings.Timing; +import co.aikar.timings.Timings; + +public class GDTimings { + + public static final Timing BLOCK_BREAK_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockBreak"); + public static final Timing BLOCK_COLLIDE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockCollide"); + public static final Timing BLOCK_NOTIFY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockNotify"); + public static final Timing BLOCK_PLACE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockPlace"); + public static final Timing BLOCK_POST_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockPost"); + public static final Timing BLOCK_PRE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onBlockPre"); + public static final Timing ENTITY_EXPLOSION_PRE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityExplosionPre"); + public static final Timing ENTITY_EXPLOSION_DETONATE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityExplosionDetonate"); + public static final Timing ENTITY_ATTACK_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityAttack"); + public static final Timing ENTITY_COLLIDE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityCollide"); + public static final Timing ENTITY_DAMAGE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityDamage"); + public static final Timing ENTITY_DAMAGE_MONITOR_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityDamageMonitor"); + public static final Timing ENTITY_DEATH_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityDeath"); + public static final Timing ENTITY_DROP_ITEM_DEATH_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityDropDeathItem"); + public static final Timing ENTITY_MOUNT_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityMount"); + public static final Timing ENTITY_MOVE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityMove"); + public static final Timing ENTITY_SPAWN_PRE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntitySpawnPre"); + public static final Timing ENTITY_SPAWN_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntitySpawn"); + public static final Timing ENTITY_TELEPORT_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onEntityTeleport"); + public static final Timing PLAYER_CHANGE_HELD_ITEM_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerChangeHeldItem"); + public static final Timing PLAYER_CHAT_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerChat"); + public static final Timing PLAYER_COMMAND_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerCommand"); + public static final Timing PLAYER_DEATH_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerDeath"); + public static final Timing PLAYER_DISPENSE_ITEM_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerDispenseItem"); + public static final Timing PLAYER_LOGIN_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerLogin"); + public static final Timing PLAYER_HANDLE_SHOVEL_ACTION = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerHandleShovelAction"); + public static final Timing PLAYER_INTERACT_BLOCK_PRIMARY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractBlockPrimary"); + public static final Timing PLAYER_INTERACT_BLOCK_SECONDARY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractBlockSecondary"); + public static final Timing PLAYER_INTERACT_ENTITY_PRIMARY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractEntityPrimary"); + public static final Timing PLAYER_INTERACT_ENTITY_SECONDARY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractEntitySecondary"); + public static final Timing PLAYER_INTERACT_INVENTORY_CLICK_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractInventoryClick"); + public static final Timing PLAYER_INTERACT_INVENTORY_CLOSE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractInventoryClose"); + public static final Timing PLAYER_INTERACT_INVENTORY_OPEN_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInteractInventoryOpen"); + public static final Timing PLAYER_INVESTIGATE_CLAIM = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerInvestigateClaim"); + public static final Timing PLAYER_JOIN_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerJoin"); + public static final Timing PLAYER_KICK_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerKick"); + public static final Timing PLAYER_PICKUP_ITEM_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerPickupItem"); + public static final Timing PLAYER_QUIT_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerQuit"); + public static final Timing PLAYER_RESPAWN_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerRespawn"); + public static final Timing PLAYER_USE_ITEM_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onPlayerUseItem"); + public static final Timing SIGN_CHANGE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onSignChange"); + public static final Timing PROJECTILE_IMPACT_BLOCK_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onProjectileImpactBlock"); + public static final Timing PROJECTILE_IMPACT_ENTITY_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onProjectileImpactEntity"); + public static final Timing EXPLOSION_PRE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onExplosionPre"); + public static final Timing EXPLOSION_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onExplosionDetonate"); + public static final Timing CLAIM_GETCLAIM = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "getClaimAt"); + public static final Timing WORLD_LOAD_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onWorldSave"); + public static final Timing WORLD_SAVE_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onWorldSave"); + public static final Timing WORLD_UNLOAD_EVENT = Timings.of(GriefDefenderPlugin.getInstance().pluginContainer, "onWorldSave"); +} diff --git a/sponge/src/main/java/com/griefdefender/GDVersion.java b/sponge/src/main/java/com/griefdefender/GDVersion.java new file mode 100644 index 0000000..14dc426 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GDVersion.java @@ -0,0 +1,44 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import com.google.inject.Singleton; +import com.griefdefender.api.Version; + +@Singleton +public class GDVersion implements Version { + + public static final double API_VERSION = 0.91; + + @Override + public double getApiVersion() { + return API_VERSION; + } + + @Override + public String getImplementationVersion() { + return GriefDefenderPlugin.IMPLEMENTATION_VERSION; + } +} diff --git a/sponge/src/main/java/com/griefdefender/GriefDefenderPlugin.java b/sponge/src/main/java/com/griefdefender/GriefDefenderPlugin.java new file mode 100644 index 0000000..e805226 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/GriefDefenderPlugin.java @@ -0,0 +1,1166 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.RegisteredCommand; +import co.aikar.commands.RootCommand; +import co.aikar.commands.SpongeCommandManager; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.google.inject.Guice; +import com.google.inject.Stage; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.economy.BankTransaction; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.ClaimContextCalculator; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.command.CommandAdjustBonusClaimBlocks; +import com.griefdefender.command.CommandCallback; +import com.griefdefender.command.CommandClaimAbandon; +import com.griefdefender.command.CommandClaimAbandonAll; +import com.griefdefender.command.CommandClaimAbandonTop; +import com.griefdefender.command.CommandClaimAdmin; +import com.griefdefender.command.CommandClaimBan; +import com.griefdefender.command.CommandClaimBank; +import com.griefdefender.command.CommandClaimBasic; +import com.griefdefender.command.CommandClaimBuy; +import com.griefdefender.command.CommandClaimBuyBlocks; +import com.griefdefender.command.CommandClaimClear; +import com.griefdefender.command.CommandClaimContract; +import com.griefdefender.command.CommandClaimCreate; +import com.griefdefender.command.CommandClaimCuboid; +import com.griefdefender.command.CommandClaimDelete; +import com.griefdefender.command.CommandClaimDeleteAll; +import com.griefdefender.command.CommandClaimDeleteAllAdmin; +import com.griefdefender.command.CommandClaimDeleteTop; +import com.griefdefender.command.CommandClaimExpand; +import com.griefdefender.command.CommandClaimFarewell; +import com.griefdefender.command.CommandClaimFlag; +import com.griefdefender.command.CommandClaimFlagDebug; +import com.griefdefender.command.CommandClaimFlagGroup; +import com.griefdefender.command.CommandClaimFlagPlayer; +import com.griefdefender.command.CommandClaimFlagReset; +import com.griefdefender.command.CommandClaimGreeting; +import com.griefdefender.command.CommandClaimIgnore; +import com.griefdefender.command.CommandClaimInfo; +import com.griefdefender.command.CommandClaimInherit; +import com.griefdefender.command.CommandClaimList; +import com.griefdefender.command.CommandClaimMode; +import com.griefdefender.command.CommandClaimName; +import com.griefdefender.command.CommandClaimOption; +import com.griefdefender.command.CommandClaimOptionGroup; +import com.griefdefender.command.CommandClaimOptionPlayer; +import com.griefdefender.command.CommandClaimPermissionGroup; +import com.griefdefender.command.CommandClaimPermissionPlayer; +import com.griefdefender.command.CommandClaimSchematic; +import com.griefdefender.command.CommandClaimSell; +import com.griefdefender.command.CommandClaimSellBlocks; +import com.griefdefender.command.CommandClaimSetSpawn; +import com.griefdefender.command.CommandClaimSpawn; +import com.griefdefender.command.CommandClaimSubdivision; +import com.griefdefender.command.CommandClaimTown; +import com.griefdefender.command.CommandClaimTransfer; +import com.griefdefender.command.CommandClaimUnban; +import com.griefdefender.command.CommandClaimWorldEdit; +import com.griefdefender.command.CommandDebug; +import com.griefdefender.command.CommandGDReload; +import com.griefdefender.command.CommandGDVersion; +import com.griefdefender.command.CommandGiveBlocks; +import com.griefdefender.command.CommandGivePet; +import com.griefdefender.command.CommandHelp; +import com.griefdefender.command.CommandPagination; +import com.griefdefender.command.CommandPlayerInfo; +import com.griefdefender.command.CommandRestoreClaim; +import com.griefdefender.command.CommandRestoreNature; +import com.griefdefender.command.CommandSetAccruedClaimBlocks; +import com.griefdefender.command.CommandTownChat; +import com.griefdefender.command.CommandTownTag; +import com.griefdefender.command.CommandTrustGroup; +import com.griefdefender.command.CommandTrustGroupAll; +import com.griefdefender.command.CommandTrustList; +import com.griefdefender.command.CommandTrustPlayer; +import com.griefdefender.command.CommandTrustPlayerAll; +import com.griefdefender.command.CommandUntrustGroup; +import com.griefdefender.command.CommandUntrustGroupAll; +import com.griefdefender.command.CommandUntrustPlayer; +import com.griefdefender.command.CommandUntrustPlayerAll; +import com.griefdefender.command.gphelper.CommandAccessTrust; +import com.griefdefender.command.gphelper.CommandContainerTrust; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.category.BlacklistCategory; +import com.griefdefender.configuration.serializer.ClaimTypeSerializer; +import com.griefdefender.configuration.serializer.ComponentConfigSerializer; +import com.griefdefender.configuration.serializer.CreateModeTypeSerializer; +import com.griefdefender.configuration.serializer.CustomFlagSerializer; +import com.griefdefender.configuration.serializer.GameModeTypeSerializer; +import com.griefdefender.configuration.serializer.WeatherTypeSerializer; +import com.griefdefender.configuration.type.ConfigBase; +import com.griefdefender.configuration.type.GlobalConfig; +import com.griefdefender.economy.GDBankTransaction; +import com.griefdefender.inject.GriefDefenderImplModule; +import com.griefdefender.internal.provider.WorldEditProvider; +import com.griefdefender.internal.registry.BlockTypeRegistryModule; +import com.griefdefender.internal.registry.EntityTypeRegistryModule; +import com.griefdefender.internal.registry.GDBlockType; +import com.griefdefender.internal.registry.GDEntityType; +import com.griefdefender.internal.registry.GDItemType; +import com.griefdefender.internal.registry.ItemTypeRegistryModule; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.listener.BlockEventHandler; +import com.griefdefender.listener.EntityEventHandler; +import com.griefdefender.listener.MCClansEventHandler; +import com.griefdefender.listener.NucleusEventHandler; +import com.griefdefender.listener.PlayerEventHandler; +import com.griefdefender.listener.WorldEventHandler; +import com.griefdefender.migrator.GPSpongeMigrator; +import com.griefdefender.permission.ContextGroupKeys; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDCustomFlagDefinition; +import com.griefdefender.permission.flag.GDFlags; +import com.griefdefender.provider.LuckPermsProvider; +import com.griefdefender.provider.MCClansProvider; +import com.griefdefender.provider.NucleusProvider; +import com.griefdefender.provider.PermissionProvider; +import com.griefdefender.registry.ChatTypeRegistryModule; +import com.griefdefender.registry.ClaimTypeRegistryModule; +import com.griefdefender.registry.CreateModeTypeRegistryModule; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.registry.GameModeTypeRegistryModule; +import com.griefdefender.registry.OptionRegistryModule; +import com.griefdefender.registry.ResultTypeRegistryModule; +import com.griefdefender.registry.ShovelTypeRegistryModule; +import com.griefdefender.registry.TrustTypeRegistryModule; +import com.griefdefender.registry.WeatherTypeRegistryModule; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.storage.FileStorage; +import com.griefdefender.task.ClaimBlockTask; +import com.griefdefender.task.ClaimCleanupTask; +import com.griefdefender.task.PlayerTickTask; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.LocaleUtils; +//import org.bstats.sponge.Metrics2; +import org.slf4j.Logger; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.cause.EventContextKey; +import org.spongepowered.api.event.game.GameReloadEvent; +import org.spongepowered.api.event.game.state.GameAboutToStartServerEvent; +import org.spongepowered.api.event.game.state.GamePreInitializationEvent; +import org.spongepowered.api.event.game.state.GameStartedServerEvent; +import org.spongepowered.api.event.service.ChangeServiceProviderEvent; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.profile.GameProfile; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.permission.PermissionService; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.world.DimensionType; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.storage.WorldProperties; +import org.spongepowered.common.SpongeImplHooks; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +public class GriefDefenderPlugin { + + private static GriefDefenderPlugin instance; + public static final String MOD_ID = "GriefDefender"; + public static final EventContextKey<GriefDefenderPlugin> PLUGIN_CONTEXT = EventContextKey.builder(GriefDefenderPlugin.class) + .name(MOD_ID) + .id(MOD_ID) + .build(); + public static final String API_VERSION = GriefDefenderPlugin.class.getPackage().getSpecificationVersion(); + public static final String IMPLEMENTATION_NAME = GriefDefenderPlugin.class.getPackage().getImplementationTitle(); + public static final String IMPLEMENTATION_VERSION = GriefDefenderPlugin.class.getPackage().getImplementationVersion(); + public static String SPONGE_VERSION = "unknown"; + protected PluginContainer pluginContainer; + protected Logger logger; + //@Inject private Metrics2 metrics; + protected Path configPath; + public MessageStorage messageStorage; + public MessageDataConfig messageData; + //public Map<UUID, Random> worldGeneratorRandoms = new HashMap<>(); + public static ClaimBlockSystem CLAIM_BLOCK_SYSTEM; + + public static final String CONFIG_HEADER = IMPLEMENTATION_VERSION + "\n" + + "# If you need help with the configuration or have any issues related to GriefDefender,\n" + + "# create a ticket on https://github.com/bloodmc/GriefDefender/issues.\n" + + "# Note: If you have not purchased GriefDefender, please consider doing so to get \n" + + "# exclusive access to Discord for prompt support.\n"; + + // GP Public user info + public static final UUID PUBLIC_UUID = UUID.fromString("41C82C87-7AfB-4024-BA57-13D2C99CAE77"); + public static final UUID WORLD_USER_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + public static final UUID ADMIN_USER_UUID = UUID.fromString("11111111-1111-1111-1111-111111111111"); + public static GDPermissionUser PUBLIC_USER; + public static GDPermissionUser WORLD_USER; + public static final String PUBLIC_NAME = "[GDPublic]"; + public static final String WORLD_USER_NAME = "[GDWorld]"; + + public static GDPermissionHolder DEFAULT_HOLDER; + private SpongeCommandManager commandManager; + public BaseStorage dataStore; + + public MCClansProvider clanApiProvider; + public NucleusProvider nucleusApiProvider; + public WorldEditProvider worldEditProvider; + private PermissionProvider permissionProvider; + public PermissionService permissionService; + + public Optional<EconomyService> economyService; + public Executor executor; + public boolean permPluginInstalled = false; + + public GDBlockType createVisualBlock; + public GDItemType modificationTool; + public GDItemType investigationTool; + public int maxInspectionDistance = 100; + + public static boolean debugLogging = false; + public static boolean debugActive = false; + private Map<String, GDDebugData> debugUserMap = new HashMap<>(); + public static final Component GD_TEXT = TextComponent.builder("").append("[").append("GD", TextColor.AQUA).append("] ").build(); + public static final List<String> ID_MAP = new ArrayList<>(); + public static final List<String> ITEM_IDS = new ArrayList<>(); + public static List<Component> helpComponents = new ArrayList<>(); + + public static GriefDefenderPlugin getInstance() { + if (instance == null) { + instance = new GriefDefenderPlugin(); + } + return instance; + } + + public Path getConfigPath() { + return this.configPath; + } + + public Logger getLogger() { + return this.logger; + } + + public static void addEventLogEntry(Event event, Location<World> location, String sourceId, String targetId, GDPermissionHolder permissionSubject, String permission, String trust, Tristate result) { + final String eventName = event.getClass().getSimpleName().replace('$', '.').replace(".Impl", ""); + final String eventLocation = location == null ? "none" : location.getBlockPosition().toString(); + for (GDDebugData debugEntry : GriefDefenderPlugin.getInstance().getDebugUserMap().values()) { + final CommandSource debugSource = debugEntry.getSource(); + final User debugUser = debugEntry.getTarget(); + if (debugUser != null) { + if (permissionSubject == null) { + continue; + } + // Check event source user + if (!permissionSubject.getFriendlyName().equals(debugUser.getUniqueId().toString())) { + continue; + } + } + + String messageUser = permissionSubject.getFriendlyName(); + if (permissionSubject instanceof GDPermissionUser) { + messageUser = ((GDPermissionUser) permissionSubject).getName(); + } + + // record + if (debugEntry.isRecording()) { + permission = permission.replace("griefdefender.flag.", ""); + String messageFlag = permission; + final Flag flag = FlagRegistryModule.getInstance().getById(permission).orElse(null); + if (flag != null) { + messageFlag = flag.toString(); + } + String messageSource = sourceId == null ? "none" : sourceId; + String messageTarget = targetId == null ? "none" : targetId; + if (messageTarget.endsWith(".0")) { + messageTarget = messageTarget.substring(0, messageTarget.length() - 2); + } + if (trust == null) { + trust = "none"; + } + // Strip minecraft id on bukkit + String[] parts = messageSource.split(":"); + if (parts.length > 1 && parts[0].equalsIgnoreCase("minecraft")) { + messageSource = parts[1]; + } + parts = messageTarget.split(":"); + if (parts.length > 1 && parts[0].equalsIgnoreCase("minecraft")) { + messageTarget = parts[1]; + } + debugEntry.addRecord(messageFlag, trust, messageSource, messageTarget, eventLocation, messageUser, permission, result); + continue; + } + + final Component textEvent = TextComponent.builder("") + .append(GD_TEXT) + .append("Event: ", TextColor.GRAY) + .append(eventName == null ? TextComponent.of("Plugin").color(TextColor.GRAY) : TextComponent.of(eventName).color(TextColor.GRAY)) + .append("\n").build(); + final Component textCause = TextComponent.builder("") + .append(GD_TEXT) + .append("Cause: ", TextColor.GRAY) + .append(sourceId, TextColor.LIGHT_PURPLE) + .append("\n").build(); + final Component textLocation = TextComponent.builder("") + .append(GD_TEXT) + .append("Location: ", TextColor.GRAY) + .append(eventLocation == null ? "NONE" : eventLocation).build(); + final Component textUser = TextComponent.builder("") + .append("User: ", TextColor.GRAY) + .append(messageUser, TextColor.GOLD) + .append("\n").build(); + final Component textLocationAndUser = TextComponent.builder("") + .append(textLocation) + .append(" ") + .append(textUser).build(); + Component textContext = null; + Component textPermission = null; + if (targetId != null) { + textContext = TextComponent.builder("") + .append(GD_TEXT) + .append("Target: ", TextColor.GRAY) + .append(GDPermissionManager.getInstance().getPermissionIdentifier(targetId), TextColor.YELLOW) + .append("\n").build(); + } + if (permission != null) { + textPermission = TextComponent.builder("") + .append(GD_TEXT) + .append("Permission: ", TextColor.GRAY) + .append(permission, TextColor.RED) + .append("\n").build(); + } + TextComponent.Builder textBuilder = TextComponent.builder("").append(textEvent); + if (textContext != null) { + textBuilder.append(textContext); + } else { + textBuilder.append(textCause); + } + if (textPermission != null) { + textBuilder.append(textPermission); + } + textBuilder.append(textLocationAndUser); + TextAdapter.sendComponent(debugSource, textBuilder.build()); + } + } + + @Listener + public void onChangeServiceProvider(ChangeServiceProviderEvent event) { + if (event.getNewProvider() instanceof PermissionService && this.validateSpongeVersion()) { + ((PermissionService) event.getNewProvider()).registerContextCalculator(new ClaimContextCalculator()); + } + } + + private boolean validateSpongeVersion() { + if (Sponge.getPlatform().getContainer(org.spongepowered.api.Platform.Component.IMPLEMENTATION).getName().equals("SpongeForge")) { + if (Sponge.getPlatform().getContainer(org.spongepowered.api.Platform.Component.IMPLEMENTATION).getVersion().isPresent()) { + try { + SPONGE_VERSION = Sponge.getPlatform().getContainer(org.spongepowered.api.Platform.Component.IMPLEMENTATION).getVersion().get(); + String build = SPONGE_VERSION.substring(Math.max(SPONGE_VERSION.length() - 4, 0)); + final int minSpongeBuild = 3549; + final int spongeBuild = Integer.parseInt(build); + if (spongeBuild < minSpongeBuild) { + this.logger.error("Unable to initialize plugin. Detected SpongeForge build " + spongeBuild + " but GriefDefender requires" + + " build " + minSpongeBuild + "+."); + return false; + } + } catch (NumberFormatException e) { + + } + } + } + + return true; + } + + @Listener(order = Order.LAST) + public void onGameReload(GameReloadEvent event) { + this.loadConfig(); + if (event.getSource() instanceof CommandSource) { + sendMessage((CommandSource) event.getSource(), MessageCache.getInstance().PLUGIN_RELOAD); + } + } + + public void onPreInit(GamePreInitializationEvent event, Logger logger, Path path, PluginContainer pluginContainer) { + this.logger = logger; + this.configPath = path; + this.pluginContainer = pluginContainer; + if (!validateSpongeVersion() || Sponge.getPlatform().getType().isClient()) { + return; + } + + this.permissionService = Sponge.getServiceManager().provide(PermissionService.class).orElse(null); + if (this.permissionService == null) { + this.logger.error("Unable to initialize plugin. GriefPrevention requires a permissions plugin such as LuckPerms."); + return; + } + this.permissionProvider = new LuckPermsProvider(); + + instance = this; + this.getLogger().info("Grief Prevention boot start."); + this.getLogger().info("Finished loading configuration."); + DEFAULT_HOLDER = new GDPermissionHolder("default"); + PUBLIC_USER = new GDPermissionUser(PUBLIC_UUID, PUBLIC_NAME); + WORLD_USER = new GDPermissionUser(WORLD_USER_UUID, WORLD_USER_NAME); + this.getLogger().info("Registering GriefDefender API..."); + Guice.createInjector(Stage.PRODUCTION, new GriefDefenderImplModule()); + + ChatTypeRegistryModule.getInstance().registerDefaults(); + ClaimTypeRegistryModule.getInstance().registerDefaults(); + ShovelTypeRegistryModule.getInstance().registerDefaults(); + TrustTypeRegistryModule.getInstance().registerDefaults(); + FlagRegistryModule.getInstance().registerDefaults(); + ResultTypeRegistryModule.getInstance().registerDefaults(); + EntityTypeRegistryModule.getInstance().registerDefaults(); + BlockTypeRegistryModule.getInstance().registerDefaults(); + ItemTypeRegistryModule.getInstance().registerDefaults(); + CreateModeTypeRegistryModule.getInstance().registerDefaults(); + GameModeTypeRegistryModule.getInstance().registerDefaults(); + WeatherTypeRegistryModule.getInstance().registerDefaults(); + OptionRegistryModule.getInstance().registerDefaults(); + GriefDefender.getRegistry().registerBuilderSupplier(Claim.Builder.class, GDClaim.ClaimBuilder::new); + GriefDefender.getRegistry().registerBuilderSupplier(BankTransaction.Builder.class, GDBankTransaction.BankTransactionBuilder::new); + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), GriefDefenderPlugin.getInstance()); + } + + @Listener + public void onServerAboutToStart(GameAboutToStartServerEvent event) { + if (!validateSpongeVersion()) { + return; + } + + this.loadConfig(); + this.registerBaseCommands(); + this.executor = Executors.newFixedThreadPool(GriefDefenderPlugin.getGlobalConfig().getConfig().thread.numExecutorThreads); + final Path migratedPath = this.configPath.resolve("_gpSpongeMigrated"); + if (GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpSpongeMigrator && !Files.exists(migratedPath)) { + GPSpongeMigrator.getInstance().migrateData(); + try { + Files.createFile(this.configPath.resolve("_gpSpongeMigrated")); + } catch (IOException e) { + e.printStackTrace(); + } + } + this.economyService = Sponge.getServiceManager().provide(EconomyService.class); + if (this.economyService.isPresent()) { + this.logger.info("GriefDefender economy integration enabled."); + if (GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + this.logger.info("Economy mode enabled!. Claimblocks will be disabled..."); + } + } else if (GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + this.logger.error("No economy plugin found! Unable to initialize economy plugin."); + return; + } + + if (Sponge.getPluginManager().getPlugin("mcclans").isPresent()) { + this.clanApiProvider = new MCClansProvider(); + } + if (Sponge.getPluginManager().getPlugin("nucleus").isPresent()) { + this.nucleusApiProvider = new NucleusProvider(); + } + if (Sponge.getPluginManager().getPlugin("worldedit").isPresent() || Sponge.getPluginManager().getPlugin("fastasyncworldedit").isPresent()) { + this.worldEditProvider = new WorldEditProvider(); + } + + if (this.dataStore == null) { + try { + this.dataStore = new FileStorage(); + this.dataStore.initialize(); + } catch (Exception e) { + this.getLogger().info("Unable to initialize file storage."); + this.getLogger().info(e.getMessage()); + e.printStackTrace(); + return; + } + } + + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new BlockEventHandler(dataStore)); + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new PlayerEventHandler(dataStore, this)); + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new EntityEventHandler(dataStore)); + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new WorldEventHandler()); + if (this.nucleusApiProvider != null) { + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new NucleusEventHandler()); + this.nucleusApiProvider.registerTokens(); + } + if (this.clanApiProvider != null) { + Sponge.getEventManager().registerListeners(GDBootstrap.getInstance(), new MCClansEventHandler()); + } + + PUBLIC_USER = new GDPermissionUser(Sponge.getServiceManager().provide(UserStorageService.class).get() + .getOrCreate(GameProfile.of(GriefDefenderPlugin.PUBLIC_UUID, GriefDefenderPlugin.PUBLIC_NAME))); + WORLD_USER = new GDPermissionUser(Sponge.getServiceManager().provide(UserStorageService.class).get() + .getOrCreate(GameProfile.of(GriefDefenderPlugin.WORLD_USER_UUID, GriefDefenderPlugin.WORLD_USER_NAME))); + + // run cleanup task + int cleanupTaskInterval = GriefDefenderPlugin.getGlobalConfig().getConfig().claim.expirationCleanupInterval; + if (cleanupTaskInterval > 0) { + ClaimCleanupTask cleanupTask = new ClaimCleanupTask(); + Sponge.getScheduler().createTaskBuilder().interval(10, TimeUnit.SECONDS).execute(cleanupTask) + .submit(GDBootstrap.getInstance()); + } + } + + @Listener + public void onServerStarted(GameStartedServerEvent event) { + if (!validateSpongeVersion()) { + return; + } + + final boolean resetMigration = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetMigrations; + final boolean resetClaimData = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetAccruedClaimBlocks; + final int migration2dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateAreaRate; + final int migration3dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateVolumeRate; + boolean migrate = false; + if (resetMigration || resetClaimData || (migration2dRate > -1 && GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.AREA) + || (migration3dRate > -1 && GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME)) { + migrate = true; + } + + if (migrate) { + List<GDPlayerData> playerDataList = new ArrayList<>(); + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + final GDClaimManager claimWorldManager = this.dataStore.getClaimWorldManager(Sponge.getServer().getDefaultWorld().get().getUniqueId()); + claimWorldManager.resetPlayerData(); + playerDataList = new ArrayList<>(claimWorldManager.getPlayerDataMap().values()); + for (GDPlayerData playerData : playerDataList) { + if (!Sponge.getServer().getPlayer(playerData.playerID).isPresent() && playerData.getClaims().isEmpty()) { + playerData.onDisconnect(); + claimWorldManager.removePlayer(playerData.playerID); + } + } + } + if (!BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + for (World world : Sponge.getServer().getWorlds()) { + final GDClaimManager claimWorldManager = this.dataStore.getClaimWorldManager(world.getUniqueId()); + playerDataList = new ArrayList<>(claimWorldManager.getPlayerDataMap().values()); + for (GDPlayerData playerData : playerDataList) { + if (!Sponge.getServer().getPlayer(playerData.playerID).isPresent() && playerData.getClaims().isEmpty()) { + playerData.onDisconnect(); + claimWorldManager.removePlayer(playerData.playerID); + } + } + } + } + GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetMigrations = false; + GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetAccruedClaimBlocks = false; + GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateAreaRate = -1; + GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateVolumeRate = -1; + GriefDefenderPlugin.getGlobalConfig().save(); + } + + if (GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpBukkitMigrator) { + GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpBukkitMigrator = false; + GriefDefenderPlugin.getGlobalConfig().save(); + } + + Sponge.getScheduler().createTaskBuilder().intervalTicks(100).execute(new PlayerTickTask()) + .submit(GDBootstrap.getInstance()); + Sponge.getScheduler().createTaskBuilder().interval(5, TimeUnit.MINUTES).execute(new ClaimBlockTask()) + .submit(GDBootstrap.getInstance()); + this.logger.info("Loaded successfully."); + } + + @Listener + public void onGameReloadEvent(GameReloadEvent event) { + this.loadConfig(); + } + + public void registerBaseCommands() { + /*Sponge.getCommandManager().register(this, CommandSpec.builder() + .description(Text.of("Lists detailed information on each command.")) + .permission(GPPermissions.COMMAND_HELP) + .executor(new CommandHelp()) + .build(), "gphelp");*/ + + SpongeCommandManager manager = new SpongeCommandManager(this.pluginContainer); + this.commandManager = manager; + manager.getCommandReplacements().addReplacement("griefdefender", "gd|griefdefender"); + manager.registerCommand(new CommandAccessTrust()); + manager.registerCommand(new CommandAdjustBonusClaimBlocks()); + manager.registerCommand(new CommandCallback()); + manager.registerCommand(new CommandClaimAbandon()); + manager.registerCommand(new CommandClaimAbandonAll()); + manager.registerCommand(new CommandClaimAbandonTop()); + manager.registerCommand(new CommandClaimAdmin()); + manager.registerCommand(new CommandClaimBan()); + manager.registerCommand(new CommandClaimBank()); + manager.registerCommand(new CommandClaimBasic()); + manager.registerCommand(new CommandClaimBuy()); + manager.registerCommand(new CommandClaimBuyBlocks()); + manager.registerCommand(new CommandClaimClear()); + manager.registerCommand(new CommandClaimContract()); + manager.registerCommand(new CommandClaimCreate()); + manager.registerCommand(new CommandClaimCuboid()); + manager.registerCommand(new CommandClaimDelete()); + manager.registerCommand(new CommandClaimDeleteAll()); + manager.registerCommand(new CommandClaimDeleteAllAdmin()); + manager.registerCommand(new CommandClaimDeleteTop()); + manager.registerCommand(new CommandClaimExpand()); + manager.registerCommand(new CommandClaimFarewell()); + manager.registerCommand(new CommandClaimFlag()); + manager.registerCommand(new CommandClaimFlagDebug()); + manager.registerCommand(new CommandClaimFlagGroup()); + manager.registerCommand(new CommandClaimFlagPlayer()); + manager.registerCommand(new CommandClaimFlagReset()); + manager.registerCommand(new CommandClaimGreeting()); + manager.registerCommand(new CommandClaimIgnore()); + manager.registerCommand(new CommandClaimInfo()); + manager.registerCommand(new CommandClaimInherit()); + manager.registerCommand(new CommandClaimList()); + manager.registerCommand(new CommandClaimMode()); + manager.registerCommand(new CommandClaimName()); + manager.registerCommand(new CommandClaimOption()); + manager.registerCommand(new CommandClaimOptionGroup()); + manager.registerCommand(new CommandClaimOptionPlayer()); + manager.registerCommand(new CommandClaimPermissionGroup()); + manager.registerCommand(new CommandClaimPermissionPlayer()); + manager.registerCommand(new CommandClaimSchematic()); + manager.registerCommand(new CommandClaimSell()); + manager.registerCommand(new CommandClaimSellBlocks()); + manager.registerCommand(new CommandClaimSetSpawn()); + manager.registerCommand(new CommandClaimSpawn()); + manager.registerCommand(new CommandClaimSubdivision()); + manager.registerCommand(new CommandClaimTown()); + manager.registerCommand(new CommandClaimTransfer()); + manager.registerCommand(new CommandClaimUnban()); + manager.registerCommand(new CommandClaimWorldEdit()); + manager.registerCommand(new CommandContainerTrust()); + manager.registerCommand(new CommandDebug()); + manager.registerCommand(new CommandGDReload()); + manager.registerCommand(new CommandGDVersion()); + manager.registerCommand(new CommandGiveBlocks()); + manager.registerCommand(new CommandGivePet()); + manager.registerCommand(new CommandHelp()); + manager.registerCommand(new CommandPagination()); + manager.registerCommand(new CommandPlayerInfo()); + manager.registerCommand(new CommandRestoreClaim()); + manager.registerCommand(new CommandRestoreNature()); + manager.registerCommand(new CommandSetAccruedClaimBlocks()); + manager.registerCommand(new CommandTownChat()); + manager.registerCommand(new CommandTownTag()); + manager.registerCommand(new CommandTrustGroup()); + manager.registerCommand(new CommandTrustPlayer()); + manager.registerCommand(new CommandTrustGroupAll()); + manager.registerCommand(new CommandTrustPlayerAll()); + manager.registerCommand(new CommandUntrustGroup()); + manager.registerCommand(new CommandUntrustPlayer()); + manager.registerCommand(new CommandUntrustGroupAll()); + manager.registerCommand(new CommandUntrustPlayerAll()); + manager.registerCommand(new CommandTrustList()); + manager.enableUnstableAPI("help"); + + final Map<String, Component> helpMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + // Generate help text + RootCommand rootCommand = getCommandManager().getRootCommand("gd"); + for (BaseCommand child : rootCommand.getChildren()) { + for (RegisteredCommand registeredCommand : child.getRegisteredCommands()) { + if (helpMap.get(registeredCommand.getPrefSubCommand()) != null) { + continue; + } + TextComponent permissionText = TextComponent.builder("") + .append("Permission: ", TextColor.GOLD) + .append(registeredCommand.getRequiredPermissions() == null ? "None" : String.join(",", registeredCommand.getRequiredPermissions()), TextColor.GRAY) + .build(); + + TextComponent argumentsText = TextComponent.builder("") + //.append("Arguments: ", TextColor.AQUA) + .append(registeredCommand.getSyntaxText() == null ? "Arguments: None" : registeredCommand.getSyntaxText(), TextColor.GREEN) + .build(); + + final TextComponent hoverText = TextComponent.builder("") + .append("Command: ", TextColor.AQUA) + .append(registeredCommand.getPrefSubCommand() + "\n", TextColor.GREEN) + .append("Description: ", TextColor.AQUA) + .append(registeredCommand.getHelpText() + "\n", TextColor.GREEN) + .append("Arguments: ", TextColor.AQUA) + .append(argumentsText) + .append("\n") + .append(permissionText) + .build(); + + final TextComponent commandText = TextComponent.builder("") + .append("/gd " + registeredCommand.getPrefSubCommand(), TextColor.GREEN) + .hoverEvent(HoverEvent.showText(hoverText)) + .clickEvent(ClickEvent.suggestCommand("/gd " + registeredCommand.getPrefSubCommand())) + .build(); + helpMap.put(registeredCommand.getPrefSubCommand(), commandText); + } + } + helpComponents = new ArrayList<>(helpMap.values()); + + NMSUtil.getInstance().populateTabComplete(); + ID_MAP.add("any"); + ID_MAP.add(ContextGroupKeys.AMBIENT); + ID_MAP.add(ContextGroupKeys.ANIMAL); + ID_MAP.add(ContextGroupKeys.AQUATIC); + ID_MAP.add(ContextGroupKeys.FOOD); + ID_MAP.add(ContextGroupKeys.MONSTER); + ID_MAP.add("#minecraft:ambient"); + ID_MAP.add("#minecraft:animal"); + ID_MAP.add("#minecraft:aquatic"); + ID_MAP.add("#minecraft:monster"); + ID_MAP.add("#minecraft:food"); + + // commands + ID_MAP.add("griefdefender:cf"); + ID_MAP.add("griefdefender:cfg"); + ID_MAP.add("griefdefender:cfp"); + ID_MAP.add("griefdefender:cog"); + ID_MAP.add("griefdefender:cop"); + ID_MAP.add("griefdefender:cpg"); + ID_MAP.add("griefdefender:cpp"); + ID_MAP.add("griefdefender:claimflag"); + ID_MAP.add("griefdefender:claimflaggroup"); + ID_MAP.add("griefdefender:claimflagplayer"); + ID_MAP.add("any"); + ID_MAP.add("unknown"); + + // Add our callback command to spam exclusion list + // This prevents players from being kicked if clicking callbacks too fast + /*if (!org.spigotmc.SpigotConfig.spamExclusions.contains("/gd:callback")) { + org.spigotmc.SpigotConfig.spamExclusions.add("/gd:callback"); + }*/ + manager.getCommandCompletions().registerCompletion("gdplayers", c -> { + return ImmutableList.copyOf(PermissionUtil.getInstance().getAllLoadedPlayerNames()); + }); + manager.getCommandCompletions().registerCompletion("gdgroups", c -> { + return ImmutableList.copyOf(PermissionUtil.getInstance().getAllLoadedGroupNames()); + }); + manager.getCommandCompletions().registerCompletion("gdbantypes", c -> { + List<String> tabList = new ArrayList<>(); + tabList.add("block"); + tabList.add("entity"); + tabList.add("item"); + tabList.add("hand"); + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdblockfaces", c -> { + List<String> tabList = new ArrayList<>(); + tabList.add("north"); + tabList.add("east"); + tabList.add("south"); + tabList.add("west"); + tabList.add("up"); + tabList.add("down"); + tabList.add("all"); + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdclaimtypes", c -> { + List<String> tabList = new ArrayList<>(); + for (ClaimType type : ClaimTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdtrusttypes", c -> { + List<String> tabList = new ArrayList<>(); + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdflags", c -> { + List<String> tabList = new ArrayList<>(); + for (Flag type : FlagRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdoptions", c -> { + List<String> tabList = new ArrayList<>(); + for (Option type : GriefDefender.getRegistry().getAllOf(Option.class)) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdentityids", c -> { + List<String> tabList = new ArrayList<>(); + for (GDEntityType type : EntityTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdmcids", c -> { + List<String> tabList = new ArrayList<>(); + for (GDBlockType type : BlockTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + for (GDItemType type : ItemTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + for (GDEntityType type : EntityTypeRegistryModule.getInstance().getAll()) { + tabList.add(type.getName()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gdtristates", c -> { + return ImmutableList.of("true", "false", "undefined"); + }); + manager.getCommandCompletions().registerCompletion("gdcontexts", c -> { + return ImmutableList.of("context[<override|default|used_item|source|world|server|player|group>]"); + }); + manager.getCommandCompletions().registerCompletion("gdworlds", c -> { + List<String> tabList = new ArrayList<>(); + for (World world : Sponge.getServer().getWorlds()) { + tabList.add(world.getName().toLowerCase()); + } + return ImmutableList.copyOf(tabList); + }); + manager.getCommandCompletions().registerCompletion("gddummy", c -> { + return ImmutableList.of(); + }); + } + + public SpongeCommandManager getCommandManager() { + return this.commandManager; + } + + public void loadConfig() { + this.getLogger().info("Loading configuration..."); + try { + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(Component.class), new ComponentConfigSerializer()); + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(ClaimType.class), new ClaimTypeSerializer()); + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(CreateModeType.class), new CreateModeTypeSerializer()); + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(GameModeType.class), new GameModeTypeSerializer()); + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(WeatherType.class), new WeatherTypeSerializer()); + TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(GDCustomFlagDefinition.class), new CustomFlagSerializer()); + + if (Files.notExists(BaseStorage.dataLayerFolderPath)) { + Files.createDirectories(BaseStorage.dataLayerFolderPath); + } + + Path rootConfigPath = this.getConfigPath().resolve("worlds"); + BaseStorage.globalConfig = new GriefDefenderConfig<>(GlobalConfig.class, this.getConfigPath().resolve("global.conf"), null); + BaseStorage.globalConfig.getConfig().permissionCategory.refreshFlags(); + BaseStorage.globalConfig.getConfig().permissionCategory.checkOptions(); + String localeString = BaseStorage.globalConfig.getConfig().message.locale; + try { + LocaleUtils.toLocale(localeString); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + this.logger.error("Could not validate the locale '" + localeString + "'. Defaulting to 'en_US'..."); + localeString = "en_US"; + } + final Path localePath = this.getConfigPath().resolve("lang").resolve(localeString + ".conf"); + if (!localePath.toFile().exists()) { + // Check for a default locale asset and copy to lang folder + /*final Asset asset = GDBootstrap.getInstance().pluginContainer.getAsset("lang/" + localeString + ".conf").orElse(null); + if (asset != null) { + asset.copyToDirectory(localePath.getParent()); + }*/ + // Check for a default locale asset and copy to lang folder + try { + final InputStream in = getClass().getResourceAsStream("/assets/lang/" + localeString + ".conf"); + FileUtils.copyInputStreamToFile(in, localePath.toFile()); + } catch (Throwable t) { + t.printStackTrace(); + } + } + messageStorage = new MessageStorage(localePath); + messageData = messageStorage.getConfig(); + MessageCache.getInstance().loadCache(); + BaseStorage.globalConfig.getConfig().customFlags.initDefaults(); + BaseStorage.globalConfig.save(); + BaseStorage.USE_GLOBAL_PLAYER_STORAGE = BaseStorage.globalConfig.getConfig().playerdata.useGlobalPlayerDataStorage; + GDFlags.populateFlagStatus(); + PermissionHolderCache.getInstance().getOrCreatePermissionCache(GriefDefenderPlugin.DEFAULT_HOLDER).invalidateAll(); + CLAIM_BLOCK_SYSTEM = BaseStorage.globalConfig.getConfig().playerdata.claimBlockSystem; + final GDItemType defaultModTool = ItemTypeRegistryModule.getInstance().getById("minecraft:golden_shovel").orElse(null); + final GDBlockType defaultCreateVisualBlock = BlockTypeRegistryModule.getInstance().getById("minecraft:diamond_block").orElse(null); + this.createVisualBlock = BlockTypeRegistryModule.getInstance().getById(BaseStorage.globalConfig.getConfig().visual.claimCreateStartBlock).orElse(defaultCreateVisualBlock); + this.modificationTool = ItemTypeRegistryModule.getInstance().getById(BaseStorage.globalConfig.getConfig().claim.modificationTool).orElse(defaultModTool); + this.investigationTool = ItemTypeRegistryModule.getInstance().getById(BaseStorage.globalConfig.getConfig().claim.investigationTool).orElse(ItemTypeRegistryModule.getInstance().getById("minecraft:stick").get()); + this.maxInspectionDistance = BaseStorage.globalConfig.getConfig().general.maxClaimInspectionDistance; + if (this.dataStore != null) { + for (World world : Sponge.getGame().getServer().getWorlds()) { + DimensionType dimType = world.getProperties().getDimensionType(); + final String[] parts = dimType.getId().split(":"); + final Path dimPath = rootConfigPath.resolve(parts[0]).resolve(dimType.getName()); + if (!Files.exists(dimPath.resolve(world.getProperties().getWorldName()))) { + try { + Files.createDirectories(rootConfigPath.resolve(dimType.getId()).resolve(world.getName())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + GriefDefenderConfig<ConfigBase> dimConfig = new GriefDefenderConfig<>(ConfigBase.class, dimPath.resolve("dimension.conf"), BaseStorage.globalConfig); + GriefDefenderConfig<ConfigBase> worldConfig = new GriefDefenderConfig<>(ConfigBase.class, dimPath.resolve(world.getProperties().getWorldName()).resolve("world.conf"), dimConfig); + + BaseStorage.dimensionConfigMap.put(world.getProperties().getUniqueId(), dimConfig); + BaseStorage.worldConfigMap.put(world.getProperties().getUniqueId(), worldConfig); + + // refresh player data + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + for (GDPlayerData playerData : claimManager.getPlayerDataMap().values()) { + if (playerData.playerID.equals(WORLD_USER_UUID) || playerData.playerID.equals(ADMIN_USER_UUID) || playerData.playerID.equals(PUBLIC_UUID)) { + continue; + } + playerData.refreshPlayerOptions(); + } + + if (GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpBukkitMigrator) { + GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpBukkitMigrator = false; + GriefDefenderPlugin.getGlobalConfig().save(); + } + if (this.worldEditProvider != null) { + this.getLogger().info("Loading schematics for world " + world.getName() + "..."); + this.worldEditProvider.loadSchematics(world); + } + } + // refresh default permissions + this.dataStore.setDefaultGlobalPermissions(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void sendClaimDenyMessage(GDClaim claim, CommandSource src, Component message) { + if (claim.getData() != null && !claim.getData().allowDenyMessages()) { + return; + } + + sendMessage(src, message); + } + + public static void sendMessage(CommandSource src, Component message) { + if (src == null) { + return; + } + if (src instanceof Player && SpongeImplHooks.isFakePlayer((net.minecraft.entity.Entity) src)) { + return; + } + if (message == TextComponent.empty() || message == null) { + return; + } + + if (src == null) { + GriefDefenderPlugin.getInstance().getLogger().warn(PlainComponentSerializer.INSTANCE.serialize(message)); + } else { + TextAdapter.sendComponent(src, message); + } + } + + public static GriefDefenderConfig<?> getActiveConfig(WorldProperties worldProperties) { + return getActiveConfig(worldProperties.getUniqueId()); + } + + public static GriefDefenderConfig<? extends ConfigBase> getActiveConfig(UUID worldUniqueId) { + GriefDefenderConfig<ConfigBase> config = BaseStorage.worldConfigMap.get(worldUniqueId); + if (config != null) { + return config; + } + + config = BaseStorage.dimensionConfigMap.get(worldUniqueId); + if (config != null) { + return config; + } + + return BaseStorage.globalConfig; + } + + public static GriefDefenderConfig<GlobalConfig> getGlobalConfig() { + return BaseStorage.globalConfig; + } + + public boolean claimsEnabledForWorld(UUID worldUniqueId) { + return GriefDefenderPlugin.getActiveConfig(worldUniqueId).getConfig().claim.claimsEnabled == 1; + } + + public int getSeaLevel(World world) { + return world.getDimension().getMinimumSpawnHeight(); + } + + public Map<String, GDDebugData> getDebugUserMap() { + return this.debugUserMap; + } + + public static GDPermissionUser getOrCreateUser(UUID uuid) { + if (uuid == null) { + return null; + } + + if (uuid == PUBLIC_UUID) { + return PUBLIC_USER; + } + if (uuid == WORLD_USER_UUID) { + return WORLD_USER; + } + + // check the cache + return PermissionHolderCache.getInstance().getOrCreateUser(uuid); + } + + public static boolean isSourceIdBlacklisted(String flag, Object source, WorldProperties worldProperties) { + return isSourceIdBlacklisted(flag, source, worldProperties.getUniqueId()); + } + + public static boolean isSourceIdBlacklisted(String flag, Object source, UUID worldUniqueId) { + final List<String> flagList = GriefDefenderPlugin.getGlobalConfig().getConfig().blacklist.flagIdBlacklist.get(flag); + final boolean checkFlag = flagList != null && !flagList.isEmpty(); + final boolean checkGlobal = !GriefDefenderPlugin.getGlobalConfig().getConfig().blacklist.globalSourceBlacklist.isEmpty(); + if (!checkFlag && !checkGlobal) { + return false; + } + + final GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(worldUniqueId); + final String id = GDPermissionManager.getInstance().getPermissionIdentifier(source); + final String idNoMeta = GDPermissionManager.getInstance().getIdentifierWithoutMeta(id); + + // Check global + if (checkGlobal) { + final BlacklistCategory blacklistCategory = activeConfig.getConfig().blacklist; + final List<String> globalSourceBlacklist = blacklistCategory.getGlobalSourceBlacklist(); + if (globalSourceBlacklist == null) { + return false; + } + for (String str : globalSourceBlacklist) { + if (FilenameUtils.wildcardMatch(id, str)) { + return true; + } + if (FilenameUtils.wildcardMatch(idNoMeta, str)) { + return true; + } + } + } + // Check flag + if (checkFlag) { + for (String str : flagList) { + if (FilenameUtils.wildcardMatch(id, str)) { + return true; + } + if (FilenameUtils.wildcardMatch(idNoMeta, str)) { + return true; + } + } + } + + return false; + } + + public static boolean isTargetIdBlacklisted(String flag, Object target, WorldProperties worldProperties) { + return isTargetIdBlacklisted(flag, target, worldProperties.getUniqueId()); + } + + public static boolean isTargetIdBlacklisted(String flag, Object target, UUID worldUniqueId) { + final List<String> flagList = GriefDefenderPlugin.getGlobalConfig().getConfig().blacklist.flagIdBlacklist.get(flag); + final boolean checkFlag = flagList != null && !flagList.isEmpty(); + final boolean checkGlobal = !GriefDefenderPlugin.getGlobalConfig().getConfig().blacklist.globalTargetBlacklist.isEmpty(); + if (!checkFlag && !checkGlobal) { + return false; + } + + final GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(worldUniqueId); + final String id = GDPermissionManager.getInstance().getPermissionIdentifier(target); + final String idNoMeta = GDPermissionManager.getInstance().getIdentifierWithoutMeta(id); + + // Check global + if (checkGlobal) { + final BlacklistCategory blacklistCategory = activeConfig.getConfig().blacklist; + final List<String> globalTargetBlacklist = blacklistCategory.getGlobalTargetBlacklist(); + if (globalTargetBlacklist == null) { + return false; + } + for (String str : globalTargetBlacklist) { + if (FilenameUtils.wildcardMatch(id, str)) { + return true; + } + if (FilenameUtils.wildcardMatch(idNoMeta, str)) { + return true; + } + } + } + // Check flag + if (checkFlag) { + for (String str : flagList) { + if (FilenameUtils.wildcardMatch(id, str)) { + return true; + } + if (FilenameUtils.wildcardMatch(idNoMeta, str)) { + return true; + } + } + } + + return false; + } + + public boolean isEconomyModeEnabled() { + return GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode; + } + + public WorldEditProvider getWorldEditProvider() { + return this.worldEditProvider; + } + + public PermissionProvider getPermissionProvider() { + return this.permissionProvider; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/cache/EventResultCache.java b/sponge/src/main/java/com/griefdefender/cache/EventResultCache.java new file mode 100644 index 0000000..e330cfd --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/cache/EventResultCache.java @@ -0,0 +1,78 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.cache; + +import java.util.UUID; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.claim.GDClaim; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.Sponge; + +public class EventResultCache { + + public final String eventFlag; + public final Tristate lastResult; + public final int lastTickCounter; + public final UUID lastClaim; + public final String lastTrust; + + public EventResultCache(Claim claim, String flag, Tristate result) { + this(claim, flag, result, null); + } + + public EventResultCache(Claim claim, String flag, Tristate result, String trust) { + this.eventFlag = flag; + this.lastClaim = claim.getUniqueId(); + this.lastResult = result; + this.lastTickCounter = Sponge.getServer().getRunningTimeTicks(); + this.lastTrust = trust == null ? "cache" : trust; + } + + public Tristate checkEventResultCache(GDClaim claim) { + return this.checkEventResultCache(claim, null); + } + + public Tristate checkEventResultCache(GDClaim claim, String flag) { + if (Sponge.getServer().getRunningTimeTicks() > this.lastTickCounter) { + return Tristate.UNDEFINED; + } + + if (claim.getUniqueId().equals(this.lastClaim)) { + if (flag != null && this.eventFlag != null && !this.eventFlag.equalsIgnoreCase(flag)) { + return Tristate.UNDEFINED; + } + return this.lastResult; + } + + return Tristate.UNDEFINED; + } +} diff --git a/sponge/src/main/java/com/griefdefender/cache/MessageCache.java b/sponge/src/main/java/com/griefdefender/cache/MessageCache.java new file mode 100644 index 0000000..5894189 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/cache/MessageCache.java @@ -0,0 +1,772 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.cache; + +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.flag.GDFlag; +import com.griefdefender.permission.option.GDOption; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.registry.OptionRegistryModule; + +import net.kyori.text.Component; + +public class MessageCache { + + private static MessageCache instance; + + static { + instance = new MessageCache(); + } + + public static MessageCache getInstance() { + return instance; + } + + public Component ABANDON_ALL_DELAY_WARNING; + public Component ABANDON_ALL_WARNING; + public Component ABANDON_CLAIM_MISSING; + public Component ABANDON_TOP_LEVEL; + public Component ABANDON_TOWN_CHILDREN; + public Component ABANDON_WARNING; + public Component BANK_CLICK_VIEW_TRANSACTIONS; + public Component BANK_DEPOSIT_NO_FUNDS; + public Component BANK_TITLE_TRANSACTIONS; + public Component BANK_TAX_SYSTEM_DISABLED; + public Component CLAIM_AUTOMATIC_NOTIFICATION; + public Component CLAIM_CHEST_CONFIRMATION; + public Component CLAIM_CHILDREN_WARNING; + public Component CLAIM_DISABLED_WORLD; + public Component CLAIM_FAREWELL_CLEAR; + public Component CLAIM_GREETING_CLEAR; + public Component CLAIM_IGNORE; + public Component CLAIM_NO_CLAIMS; + public Component CLAIM_NOT_FOUND; + public Component CLAIM_NOT_YOURS; + public Component CLAIM_OWNER_ALREADY; + public Component CLAIM_OWNER_ONLY; + public Component CLAIM_RESPECTING; + public Component CLAIM_RESTORE_SUCCESS; + public Component CLAIM_TOO_FAR; + public Component CLAIMINFO_UI_ADMIN_SETTINGS; + public Component CLAIMINFO_UI_BANK_INFO; + public Component CLAIMINFO_UI_CLAIM_EXPIRATION; + public Component CLAIMINFO_UI_CLICK_ADMIN; + public Component CLAIMINFO_UI_CLICK_BANK; + public Component CLAIMINFO_UI_CLICK_TOGGLE; + public Component CLAIMINFO_UI_DENY_MESSAGES; + public Component CLAIMINFO_UI_FLAG_OVERRIDES; + public Component CLAIMINFO_UI_FOR_SALE; + public Component CLAIMINFO_UI_INHERIT_PARENT; + public Component CLAIMINFO_UI_LAST_ACTIVE; + public Component CLAIMINFO_UI_NORTH_CORNERS; + public Component CLAIMINFO_UI_PVP_OVERRIDES; + public Component CLAIMINFO_UI_REQUIRES_CLAIM_BLOCKS; + public Component CLAIMINFO_UI_RETURN_BANKINFO; + public Component CLAIMINFO_UI_RETURN_CLAIMINFO; + public Component CLAIMINFO_UI_RETURN_SETTINGS; + public Component CLAIMINFO_UI_SIZE_RESTRICTIONS; + public Component CLAIMINFO_UI_SOUTH_CORNERS; + public Component CLAIMINFO_UI_TELEPORT_FEATURE; + public Component CLAIMINFO_UI_TELEPORT_SPAWN; + public Component CLAIMINFO_UI_TITLE_CLAIMINFO; + public Component CLAIMINFO_UI_TOWN_SETTINGS; + public Component CLAIMLIST_UI_CLICK_INFO; + public Component CLAIMLIST_UI_CLICK_PURCHASE; + public Component CLAIMLIST_UI_CLICK_VIEW_CHILDREN; + public Component CLAIMLIST_UI_CLICK_VIEW_CLAIMS; + public Component CLAIMLIST_UI_NO_CLAIMS_FOUND; + public Component CLAIMLIST_UI_RETURN_CLAIMSLIST; + public Component CLAIMLIST_UI_TITLE; + public Component CLAIMLIST_UI_TITLE_CHILD_CLAIMS; + public Component COMMAND_CLAIMBUY_TITLE; + public Component COMMAND_CLAIMCLEAR_UUID_DENY; + public Component COMMAND_CLAIMFLAGDEBUG_DISABLED; + public Component COMMAND_CLAIMFLAGDEBUG_ENABLED; + public Component COMMAND_CLAIMINFO_NOT_FOUND; + public Component COMMAND_CLAIMINFO_UUID_REQUIRED; + public Component COMMAND_CLAIMINHERIT_DISABLED; + public Component COMMAND_CLAIMINHERIT_ENABLED; + public Component COMMAND_CLAIMMODE_DISABLED; + public Component COMMAND_CLAIMMODE_ENABLED; + public Component COMMAND_CUBOID_DISABLED; + public Component COMMAND_CUBOID_ENABLED; + public Component COMMAND_INHERIT_ONLY_CHILD; + public Component COMMAND_INVALID; + public Component COMMAND_INVALID_PLAYER_GROUP; + public Component COMMAND_NOT_AVAILABLE_ECONOMY; + public Component COMMAND_PET_CONFIRMATION; + public Component COMMAND_PET_TRANSFER_READY; + public Component COMMAND_PET_TRANSFER_CANCEL; + public Component COMMAND_WORLDEDIT_MISSING; + public Component CREATE_CANCEL; + public Component CREATE_CUBOID_DISABLED; + public Component CREATE_OVERLAP; + public Component CREATE_OVERLAP_SHORT; + public Component CREATE_SUBDIVISION_FAIL; + public Component CREATE_SUBDIVISION_ONLY; + public Component DEBUG_NO_RECORDS; + public Component DEBUG_PASTE_SUCCESS; + public Component DEBUG_RECORD_END; + public Component DEBUG_RECORD_START; + public Component DEBUG_TIME_ELAPSED; + public Component ECONOMY_BLOCK_BUY_INVALID; + public Component ECONOMY_BLOCK_BUY_SELL_DISABLED; + public Component ECONOMY_BLOCK_NOT_AVAILABLE; + public Component ECONOMY_BLOCK_ONLY_BUY; + public Component ECONOMY_BLOCK_ONLY_SELL; + public Component ECONOMY_CLAIM_NOT_FOR_SALE; + public Component ECONOMY_CLAIM_SALE_CANCELLED; + public Component ECONOMY_NOT_INSTALLED; + public Component ECONOMY_VIRTUAL_NOT_SUPPORTED; + public Component FEATURE_NOT_AVAILABLE; + public Component FLAG_DESCRIPTION_CUSTOM_BLOCK_BREAK; + public Component FLAG_DESCRIPTION_CUSTOM_BLOCK_GROW; + public Component FLAG_DESCRIPTION_CUSTOM_BLOCK_PLACE; + public Component FLAG_DESCRIPTION_CUSTOM_BLOCK_SPREAD; + public Component FLAG_DESCRIPTION_CUSTOM_ENDERPEARL; + public Component FLAG_DESCRIPTION_CUSTOM_EXIT_PLAYER; + public Component FLAG_DESCRIPTION_CUSTOM_EXPLOSION_BLOCK; + public Component FLAG_DESCRIPTION_CUSTOM_EXPLOSION_ENTITY; + public Component FLAG_DESCRIPTION_CUSTOM_EXP_DROP; + public Component FLAG_DESCRIPTION_CUSTOM_FALL_DAMAGE; + public Component FLAG_DESCRIPTION_CUSTOM_INTERACT_BLOCK; + public Component FLAG_DESCRIPTION_CUSTOM_INTERACT_ENTITY; + public Component FLAG_DESCRIPTION_CUSTOM_INTERACT_INVENTORY; + public Component FLAG_DESCRIPTION_CUSTOM_INVINCIBLE; + public Component FLAG_DESCRIPTION_CUSTOM_ITEM_DROP; + public Component FLAG_DESCRIPTION_CUSTOM_ITEM_PICKUP; + public Component FLAG_DESCRIPTION_CUSTOM_MONSTER_DAMAGE; + public Component FLAG_DESCRIPTION_CUSTOM_PISTONS; + public Component FLAG_DESCRIPTION_CUSTOM_PORTAL_USE; + public Component FLAG_DESCRIPTION_CUSTOM_SPAWN_AQUATIC; + public Component FLAG_DESCRIPTION_CUSTOM_SPAWN_AMBIENT; + public Component FLAG_DESCRIPTION_CUSTOM_SPAWN_ANIMAL; + public Component FLAG_DESCRIPTION_CUSTOM_SPAWN_MONSTER; + public Component FLAG_DESCRIPTION_CUSTOM_TELEPORT_FROM; + public Component FLAG_DESCRIPTION_CUSTOM_TELEPORT_TO; + public Component FLAG_DESCRIPTION_CUSTOM_USE; + public Component FLAG_DESCRIPTION_CUSTOM_VEHICLE_DESTROY; + public Component FLAG_DESCRIPTION_CUSTOM_WITHER_DAMAGE; + public Component FLAG_DESCRIPTION_CUSTOM_BLOCK_TRAMPLING; + public Component FLAG_DESCRIPTION_CUSTOM_CHEST_ACCESS; + public Component FLAG_DESCRIPTION_CUSTOM_CHORUS_FRUIT_TELEPORT; + public Component FLAG_DESCRIPTION_CUSTOM_CROP_GROWTH; + public Component FLAG_DESCRIPTION_CUSTOM_DAMAGE_ANIMALS; + public Component FLAG_DESCRIPTION_CUSTOM_ENDERMAN_GRIEF; + public Component FLAG_DESCRIPTION_CUSTOM_ENTER_PLAYER; + public Component FLAG_DESCRIPTION_CUSTOM_EXPLOSION_CREEPER; + public Component FLAG_DESCRIPTION_CUSTOM_EXPLOSION_TNT; + public Component FLAG_DESCRIPTION_CUSTOM_FIRE_DAMAGE; + public Component FLAG_DESCRIPTION_CUSTOM_FIRE_SPREAD; + public Component FLAG_DESCRIPTION_CUSTOM_GRASS_GROWTH; + public Component FLAG_DESCRIPTION_CUSTOM_ICE_FORM; + public Component FLAG_DESCRIPTION_CUSTOM_ICE_MELT; + public Component FLAG_DESCRIPTION_CUSTOM_LAVA_FLOW; + public Component FLAG_DESCRIPTION_CUSTOM_LEAF_DECAY; + public Component FLAG_DESCRIPTION_CUSTOM_LIGHTNING; + public Component FLAG_DESCRIPTION_CUSTOM_LIGHTER; + public Component FLAG_DESCRIPTION_CUSTOM_MUSHROOM_GROWTH; + public Component FLAG_DESCRIPTION_CUSTOM_MYCELIUM_SPREAD; + public Component FLAG_DESCRIPTION_CUSTOM_PVP; + public Component FLAG_DESCRIPTION_CUSTOM_RIDE; + public Component FLAG_DESCRIPTION_CUSTOM_SLEEP; + public Component FLAG_DESCRIPTION_CUSTOM_SNOW_FALL; + public Component FLAG_DESCRIPTION_CUSTOM_SNOW_MELT; + public Component FLAG_DESCRIPTION_CUSTOM_SNOWMAN_TRAIL; + public Component FLAG_DESCRIPTION_CUSTOM_SOIL_DRY; + public Component FLAG_DESCRIPTION_CUSTOM_VEHICLE_PLACE; + public Component FLAG_DESCRIPTION_CUSTOM_VINE_GROWTH; + public Component FLAG_DESCRIPTION_CUSTOM_WATER_FLOW; + public Component FLAG_DESCRIPTION_BLOCK_BREAK; + public Component FLAG_DESCRIPTION_BLOCK_GROW; + public Component FLAG_DESCRIPTION_BLOCK_MODIFY; + public Component FLAG_DESCRIPTION_BLOCK_PLACE; + public Component FLAG_DESCRIPTION_BLOCK_SPREAD; + public Component FLAG_DESCRIPTION_COLLIDE_BLOCK; + public Component FLAG_DESCRIPTION_COLLIDE_ENTITY; + public Component FLAG_DESCRIPTION_COMMAND_EXECUTE; + public Component FLAG_DESCRIPTION_COMMAND_EXECUTE_PVP; + public Component FLAG_DESCRIPTION_ENTER_CLAIM; + public Component FLAG_DESCRIPTION_ENTITY_CHUNK_SPAWN; + public Component FLAG_DESCRIPTION_ENTITY_DAMAGE; + public Component FLAG_DESCRIPTION_ENTITY_RIDING; + public Component FLAG_DESCRIPTION_ENTITY_SPAWN; + public Component FLAG_DESCRIPTION_ENTITY_TELEPORT_FROM; + public Component FLAG_DESCRIPTION_ENTITY_TELEPORT_TO; + public Component FLAG_DESCRIPTION_EXIT_CLAIM; + public Component FLAG_DESCRIPTION_EXPLOSION_BLOCK; + public Component FLAG_DESCRIPTION_EXPLOSION_ENTITY; + public Component FLAG_DESCRIPTION_INTERACT_BLOCK_PRIMARY; + public Component FLAG_DESCRIPTION_INTERACT_BLOCK_SECONDARY; + public Component FLAG_DESCRIPTION_INTERACT_ENTITY_PRIMARY; + public Component FLAG_DESCRIPTION_INTERACT_ENTITY_SECONDARY; + public Component FLAG_DESCRIPTION_INTERACT_INVENTORY; + public Component FLAG_DESCRIPTION_INTERACT_ITEM_PRIMARY; + public Component FLAG_DESCRIPTION_INTERACT_ITEM_SECONDARY; + public Component FLAG_DESCRIPTION_INTERACT_INVENTORY_CLICK; + public Component FLAG_DESCRIPTION_ITEM_DROP; + public Component FLAG_DESCRIPTION_ITEM_PICKUP; + public Component FLAG_DESCRIPTION_ITEM_SPAWN; + public Component FLAG_DESCRIPTION_ITEM_USE; + public Component FLAG_DESCRIPTION_LEAF_DECAY; + public Component FLAG_DESCRIPTION_LIQUID_FLOW; + public Component FLAG_DESCRIPTION_PORTAL_USE; + public Component FLAG_DESCRIPTION_PROJECTILE_IMPACT_BLOCK; + public Component FLAG_DESCRIPTION_PROJECTILE_IMPACT_ENTITY; + public Component FLAG_RESET_SUCCESS; + public Component FLAG_RESET_WARNING; + public Component FLAG_UI_CLICK_ALLOW; + public Component FLAG_UI_CLICK_DENY; + public Component FLAG_UI_CLICK_REMOVE; + public Component FLAG_UI_INFO_CLAIM; + public Component FLAG_UI_INFO_DEFAULT; + public Component FLAG_UI_INFO_INHERIT; + public Component FLAG_UI_INFO_OVERRIDE; + public Component FLAG_UI_OVERRIDE_NO_PERMISSION; + public Component FLAG_UI_RETURN_FLAGS; + public Component LABEL_ACCESSORS; + public Component LABEL_AREA; + public Component LABEL_BLOCKS; + public Component LABEL_BUILDERS; + public Component LABEL_BUY; + public Component LABEL_CHILDREN; + public Component LABEL_CONFIRM; + public Component LABEL_CONTAINERS; + public Component LABEL_CONTEXT; + public Component LABEL_CREATED; + public Component LABEL_DISPLAYING; + public Component LABEL_EXPIRED; + public Component LABEL_FAREWELL; + public Component LABEL_FLAG; + public Component LABEL_GREETING; + public Component LABEL_GROUP; + public Component LABEL_INHERIT; + public Component LABEL_LOCATION; + public Component LABEL_MANAGERS; + public Component LABEL_NAME; + public Component LABEL_NO; + public Component LABEL_OUTPUT; + public Component LABEL_OWNER; + public Component LABEL_PERMISSION; + public Component LABEL_PLAYER; + public Component LABEL_PRICE; + public Component LABEL_RAID; + public Component LABEL_RESIZABLE; + public Component LABEL_RESULT; + public Component LABEL_SCHEMATIC; + public Component LABEL_SOURCE; + public Component LABEL_SPAWN; + public Component LABEL_TARGET; + public Component LABEL_TRUST; + public Component LABEL_TYPE; + public Component LABEL_WORLD; + public Component LABEL_UNKNOWN; + public Component LABEL_USER; + public Component LABEL_YES; + public Component MODE_ADMIN; + public Component MODE_BASIC; + public Component MODE_NATURE; + public Component MODE_SUBDIVISION; + public Component MODE_TOWN; + public Component OPTION_DESCRIPTION_ABANDON_DELAY; + public Component OPTION_DESCRIPTION_ABANDON_RETURN_RATIO; + public Component OPTION_DESCRIPTION_BLOCKS_ACCRUED_PER_HOUR; + public Component OPTION_DESCRIPTION_CHEST_EXPIRATION; + public Component OPTION_DESCRIPTION_CREATE_LIMIT; + public Component OPTION_DESCRIPTION_CREATE_MODE; + public Component OPTION_DESCRIPTION_ECONOMY_BLOCK_COST; + public Component OPTION_DESCRIPTION_ECONOMY_BLOCK_SELL_RETURN; + public Component OPTION_DESCRIPTION_EXPIRATION; + public Component OPTION_DESCRIPTION_INITIAL_BLOCKS; + public Component OPTION_DESCRIPTION_MAX_ACCRUED_BLOCKS; + public Component OPTION_DESCRIPTION_MAX_LEVEL; + public Component OPTION_DESCRIPTION_MAX_SIZE_X; + public Component OPTION_DESCRIPTION_MAX_SIZE_Y; + public Component OPTION_DESCRIPTION_MAX_SIZE_Z; + public Component OPTION_DESCRIPTION_MIN_LEVEL; + public Component OPTION_DESCRIPTION_MIN_SIZE_X; + public Component OPTION_DESCRIPTION_MIN_SIZE_Y; + public Component OPTION_DESCRIPTION_MIN_SIZE_Z; + public Component OPTION_DESCRIPTION_PLAYER_COMMAND; + public Component OPTION_DESCRIPTION_PLAYER_DENY_FLIGHT; + public Component OPTION_DESCRIPTION_PLAYER_DENY_GODMODE; + public Component OPTION_DESCRIPTION_PLAYER_DENY_HUNGER; + public Component OPTION_DESCRIPTION_PLAYER_GAMEMODE; + public Component OPTION_DESCRIPTION_PLAYER_HEALTH_REGEN; + public Component OPTION_DESCRIPTION_PLAYER_KEEP_INVENTORY; + public Component OPTION_DESCRIPTION_PLAYER_KEEP_LEVEL; + public Component OPTION_DESCRIPTION_PLAYER_WALK_SPEED; + public Component OPTION_DESCRIPTION_PLAYER_WEATHER; + public Component OPTION_DESCRIPTION_RADIUS_LIST; + public Component OPTION_DESCRIPTION_RADIUS_INSPECT; + public Component OPTION_DESCRIPTION_TAX_EXPIRATION; + public Component OPTION_DESCRIPTION_TAX_EXPIRATION_DAYS_KEEP; + public Component OPTION_DESCRIPTION_TAX_RATE; + public Component OPTION_PLAYER_DENY_FLIGHT; + public Component OWNER_ADMIN; + public Component PERMISSION_ASSIGN_WITHOUT_HAVING; + public Component PERMISSION_CLAIM_CREATE; + public Component PERMISSION_CLAIM_ENTER; + public Component PERMISSION_CLAIM_EXIT; + public Component PERMISSION_CLAIM_LIST; + public Component PERMISSION_CLAIM_RESET_FLAGS_SELF; + public Component PERMISSION_CLAIM_RESIZE; + public Component PERMISSION_CLAIM_SALE; + public Component PERMISSION_CLAIM_TRANSFER_ADMIN; + public Component PERMISSION_CLEAR; + public Component PERMISSION_CLEAR_ALL; + public Component PERMISSION_COMMAND_TRUST; + public Component PERMISSION_CUBOID; + public Component PERMISSION_EDIT_CLAIM; + public Component PERMISSION_FIRE_SPREAD; + public Component PERMISSION_FLAG_DEFAULTS; + public Component PERMISSION_FLAG_OVERRIDES; + public Component PERMISSION_FLAG_USE; + public Component PERMISSION_FLOW_LIQUID; + public Component PERMISSION_GLOBAL_OPTION; + public Component PERMISSION_GRANT; + public Component PERMISSION_GROUP_OPTION; + public Component PERMISSION_OPTION_DEFAULTS; + public Component PERMISSION_OPTION_OVERRIDES; + public Component PERMISSION_OPTION_USE; + public Component PERMISSION_OVERRIDE_DENY; + public Component PERMISSION_PLAYER_ADMIN_FLAGS; + public Component PERMISSION_PLAYER_OPTION; + public Component PERMISSION_PLAYER_VIEW_OTHERS; + public Component PERMISSION_VISUAL_CLAIMS_NEARBY; + public Component PLAYERINFO_UI_TITLE; + public Component PLUGIN_EVENT_CANCEL; + public Component PLUGIN_RELOAD; + public Component PVP_CLAIM_NOT_ALLOWED; + public Component PVP_SOURCE_NOT_ALLOWED; + public Component PVP_TARGET_NOT_ALLOWED; + public Component RESIZE_OVERLAP; + public Component RESIZE_OVERLAP_SUBDIVISION; + public Component RESIZE_SAME_LOCATION; + public Component RESIZE_START; + public Component SCHEMATIC_ABANDON_ALL_RESTORE_WARNING; + public Component SCHEMATIC_ABANDON_RESTORE_WARNING; + public Component SCHEMATIC_CREATE; + public Component SCHEMATIC_CREATE_COMPLETE; + public Component SCHEMATIC_CREATE_FAIL; + public Component SCHEMATIC_NONE; + public Component SPAWN_NOT_SET; + public Component TELEPORT_MOVE_CANCEL; + public Component TELEPORT_NO_SAFE_LOCATION; + public Component TITLE_ACCESSOR; + public Component TITLE_ALL; + public Component TITLE_BUILDER; + public Component TITLE_CLAIM; + public Component TITLE_CONTAINER; + public Component TITLE_DEFAULT; + public Component TITLE_INHERIT; + public Component TITLE_MANAGER; + public Component TITLE_OWN; + public Component TITLE_OVERRIDE; + public Component TOWN_CHAT_DISABLED; + public Component TOWN_CHAT_ENABLED; + public Component TOWN_NOT_FOUND; + public Component TOWN_NOT_IN; + public Component TOWN_OWNER; + public Component TOWN_TAG_CLEAR; + public Component TOWN_TAX_NO_CLAIMS; + public Component TRUST_CLICK_SHOW_LIST; + public Component TRUST_INVALID; + public Component TRUST_LIST_HEADER; + public Component TRUST_NO_CLAIMS; + public Component TRUST_SELF; + public Component UI_CLICK_CONFIRM; + public Component UNTRUST_NO_CLAIMS; + public Component UNTRUST_SELF; + + public void loadCache() { + ABANDON_ALL_DELAY_WARNING = MessageStorage.MESSAGE_DATA.getMessage("abandon-all-delay-warning"); + ABANDON_ALL_WARNING = MessageStorage.MESSAGE_DATA.getMessage("abandon-all-warning"); + ABANDON_CLAIM_MISSING = MessageStorage.MESSAGE_DATA.getMessage("abandon-claim-missing"); + ABANDON_TOP_LEVEL = MessageStorage.MESSAGE_DATA.getMessage("abandon-top-level"); + ABANDON_TOWN_CHILDREN = MessageStorage.MESSAGE_DATA.getMessage("abandon-town-children"); + ABANDON_WARNING = MessageStorage.MESSAGE_DATA.getMessage("abandon-warning"); + BANK_CLICK_VIEW_TRANSACTIONS = MessageStorage.MESSAGE_DATA.getMessage("bank-click-view-transactions"); + BANK_DEPOSIT_NO_FUNDS = MessageStorage.MESSAGE_DATA.getMessage("bank-deposit-no-funds"); + BANK_TAX_SYSTEM_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("bank-tax-system-disabled"); + BANK_TITLE_TRANSACTIONS = MessageStorage.MESSAGE_DATA.getMessage("bank-title-transactions"); + CLAIM_AUTOMATIC_NOTIFICATION = MessageStorage.MESSAGE_DATA.getMessage("claim-automatic-notification"); + CLAIM_CHEST_CONFIRMATION = MessageStorage.MESSAGE_DATA.getMessage("claim-chest-confirmation"); + CLAIM_CHILDREN_WARNING = MessageStorage.MESSAGE_DATA.getMessage("claim-children-warning"); + CLAIM_DISABLED_WORLD = MessageStorage.MESSAGE_DATA.getMessage("claim-disabled-world"); + CLAIM_FAREWELL_CLEAR = MessageStorage.MESSAGE_DATA.getMessage("claim-farewell-clear"); + CLAIM_GREETING_CLEAR = MessageStorage.MESSAGE_DATA.getMessage("claim-greeting-clear"); + CLAIM_IGNORE = MessageStorage.MESSAGE_DATA.getMessage("claim-ignore"); + CLAIM_NO_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("claim-no-claims"); + CLAIM_NOT_FOUND = MessageStorage.MESSAGE_DATA.getMessage("claim-not-found"); + CLAIM_NOT_YOURS = MessageStorage.MESSAGE_DATA.getMessage("claim-not-yours"); + CLAIM_OWNER_ALREADY = MessageStorage.MESSAGE_DATA.getMessage("claim-owner-already"); + CLAIM_OWNER_ONLY = MessageStorage.MESSAGE_DATA.getMessage("claim-owner-only"); + CLAIM_RESPECTING = MessageStorage.MESSAGE_DATA.getMessage("claim-respecting"); + CLAIM_RESTORE_SUCCESS = MessageStorage.MESSAGE_DATA.getMessage("claim-restore-success"); + CLAIM_TOO_FAR = MessageStorage.MESSAGE_DATA.getMessage("claim-too-far"); + CLAIMINFO_UI_ADMIN_SETTINGS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-admin-settings"); + CLAIMINFO_UI_BANK_INFO = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-bank-info"); + CLAIMINFO_UI_CLAIM_EXPIRATION = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-claim-expiration"); + CLAIMINFO_UI_CLICK_ADMIN = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-click-admin"); + CLAIMINFO_UI_CLICK_BANK = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-click-bank"); + CLAIMINFO_UI_CLICK_TOGGLE = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-click-toggle"); + CLAIMINFO_UI_DENY_MESSAGES = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-deny-messages"); + CLAIMINFO_UI_FLAG_OVERRIDES = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-flag-overrides"); + CLAIMINFO_UI_FOR_SALE = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-for-sale"); + CLAIMINFO_UI_INHERIT_PARENT = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-inherit-parent"); + CLAIMINFO_UI_LAST_ACTIVE = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-last-active"); + CLAIMINFO_UI_NORTH_CORNERS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-north-corners"); + CLAIMINFO_UI_PVP_OVERRIDES = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-pvp-overrides"); + CLAIMINFO_UI_REQUIRES_CLAIM_BLOCKS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-requires-claim-blocks"); + CLAIMINFO_UI_RETURN_BANKINFO = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-return-bankinfo"); + CLAIMINFO_UI_RETURN_CLAIMINFO = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-return-claiminfo"); + CLAIMINFO_UI_RETURN_SETTINGS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-return-settings"); + CLAIMINFO_UI_SIZE_RESTRICTIONS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-size-restrictions"); + CLAIMINFO_UI_SOUTH_CORNERS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-south-corners"); + CLAIMINFO_UI_TELEPORT_FEATURE = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-teleport-feature"); + CLAIMINFO_UI_TELEPORT_SPAWN = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-teleport-spawn"); + CLAIMINFO_UI_TITLE_CLAIMINFO = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-title-claiminfo"); + CLAIMINFO_UI_TOWN_SETTINGS = MessageStorage.MESSAGE_DATA.getMessage("claiminfo-ui-town-settings"); + CLAIMLIST_UI_CLICK_INFO = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-click-info"); + CLAIMLIST_UI_CLICK_PURCHASE = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-click-purchase"); + CLAIMLIST_UI_CLICK_VIEW_CHILDREN = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-click-view-children"); + CLAIMLIST_UI_CLICK_VIEW_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-click-view-claims"); + CLAIMLIST_UI_NO_CLAIMS_FOUND = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-no-claims-found"); + CLAIMLIST_UI_RETURN_CLAIMSLIST = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-return-claimlist"); + CLAIMLIST_UI_TITLE = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-title"); + CLAIMLIST_UI_TITLE_CHILD_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("claimlist-ui-title-child-claims"); + COMMAND_CLAIMBUY_TITLE = MessageStorage.MESSAGE_DATA.getMessage("command-claimbuy-title"); + COMMAND_CLAIMCLEAR_UUID_DENY = MessageStorage.MESSAGE_DATA.getMessage("command-claimclear-uuid-deny"); + COMMAND_CLAIMFLAGDEBUG_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claimflagdebug-disabled"); + COMMAND_CLAIMFLAGDEBUG_ENABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claimflagdebug-enabled"); + COMMAND_CLAIMINFO_NOT_FOUND = MessageStorage.MESSAGE_DATA.getMessage("command-claiminfo-not-found"); + COMMAND_CLAIMINFO_UUID_REQUIRED = MessageStorage.MESSAGE_DATA.getMessage("command-claiminfo-uuid-required"); + COMMAND_CLAIMINHERIT_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claiminherit-disabled"); + COMMAND_CLAIMINHERIT_ENABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claiminherit-enabled"); + COMMAND_CLAIMMODE_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claimmode-disabled"); + COMMAND_CLAIMMODE_ENABLED = MessageStorage.MESSAGE_DATA.getMessage("command-claimmode-enabled"); + COMMAND_CUBOID_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("command-cuboid-disabled"); + COMMAND_CUBOID_ENABLED = MessageStorage.MESSAGE_DATA.getMessage("command-cuboid-enabled"); + COMMAND_INHERIT_ONLY_CHILD = MessageStorage.MESSAGE_DATA.getMessage("command-inherit-only-child"); + COMMAND_INVALID = MessageStorage.MESSAGE_DATA.getMessage("command-invalid"); + COMMAND_INVALID_PLAYER_GROUP = MessageStorage.MESSAGE_DATA.getMessage("command-invalid-player-group"); + COMMAND_NOT_AVAILABLE_ECONOMY = MessageStorage.MESSAGE_DATA.getMessage("command-not-available-economy"); + COMMAND_PET_CONFIRMATION = MessageStorage.MESSAGE_DATA.getMessage("command-pet-confirmation"); + COMMAND_PET_TRANSFER_READY = MessageStorage.MESSAGE_DATA.getMessage("command-pet-transfer-ready"); + COMMAND_PET_TRANSFER_CANCEL = MessageStorage.MESSAGE_DATA.getMessage("command-pet-transfer-cancel"); + COMMAND_WORLDEDIT_MISSING = MessageStorage.MESSAGE_DATA.getMessage("command-worldedit-missing"); + CREATE_CANCEL = MessageStorage.MESSAGE_DATA.getMessage("create-cancel"); + CREATE_CUBOID_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("create-cuboid-disabled"); + CREATE_OVERLAP = MessageStorage.MESSAGE_DATA.getMessage("create-overlap"); + CREATE_OVERLAP_SHORT = MessageStorage.MESSAGE_DATA.getMessage("create-overlap-short"); + CREATE_SUBDIVISION_FAIL = MessageStorage.MESSAGE_DATA.getMessage("create-subdivision-fail"); + CREATE_SUBDIVISION_ONLY = MessageStorage.MESSAGE_DATA.getMessage("create-subdivision-only"); + DEBUG_NO_RECORDS = MessageStorage.MESSAGE_DATA.getMessage("debug-no-records"); + DEBUG_PASTE_SUCCESS = MessageStorage.MESSAGE_DATA.getMessage("debug-paste-success"); + DEBUG_RECORD_END = MessageStorage.MESSAGE_DATA.getMessage("debug-record-end"); + DEBUG_RECORD_START = MessageStorage.MESSAGE_DATA.getMessage("debug-record-start"); + DEBUG_TIME_ELAPSED = MessageStorage.MESSAGE_DATA.getMessage("debug-time-elapsed"); + ECONOMY_BLOCK_BUY_INVALID = MessageStorage.MESSAGE_DATA.getMessage("economy-block-buy-invalid"); + ECONOMY_BLOCK_BUY_SELL_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("economy-block-buy-sell-disabled"); + ECONOMY_BLOCK_NOT_AVAILABLE = MessageStorage.MESSAGE_DATA.getMessage("economy-block-not-available"); + ECONOMY_BLOCK_ONLY_BUY = MessageStorage.MESSAGE_DATA.getMessage("economy-block-only-buy"); + ECONOMY_BLOCK_ONLY_SELL = MessageStorage.MESSAGE_DATA.getMessage("economy-block-only-sell"); + ECONOMY_CLAIM_NOT_FOR_SALE = MessageStorage.MESSAGE_DATA.getMessage("economy-claim-not-for-sale"); + ECONOMY_CLAIM_SALE_CANCELLED = MessageStorage.MESSAGE_DATA.getMessage("economy-claim-sale-cancelled"); + ECONOMY_NOT_INSTALLED = MessageStorage.MESSAGE_DATA.getMessage("economy-not-installed"); + ECONOMY_VIRTUAL_NOT_SUPPORTED = MessageStorage.MESSAGE_DATA.getMessage("economy-virtual-not-supported"); + FEATURE_NOT_AVAILABLE = MessageStorage.MESSAGE_DATA.getMessage("feature-not-available"); + FLAG_DESCRIPTION_CUSTOM_BLOCK_BREAK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-block-break"); + FLAG_DESCRIPTION_CUSTOM_BLOCK_GROW = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-block-grow"); + FLAG_DESCRIPTION_CUSTOM_BLOCK_PLACE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-block-place"); + FLAG_DESCRIPTION_CUSTOM_BLOCK_SPREAD = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-block-spread"); + FLAG_DESCRIPTION_CUSTOM_ENDERPEARL = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-enderpearl"); + FLAG_DESCRIPTION_CUSTOM_EXIT_PLAYER = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-exit-player"); + FLAG_DESCRIPTION_CUSTOM_EXPLOSION_BLOCK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-explosion-block"); + FLAG_DESCRIPTION_CUSTOM_EXPLOSION_ENTITY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-explosion-entity"); + FLAG_DESCRIPTION_CUSTOM_EXP_DROP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-exp-drop"); + FLAG_DESCRIPTION_CUSTOM_FALL_DAMAGE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-fall-damage"); + FLAG_DESCRIPTION_CUSTOM_INTERACT_BLOCK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-interact-block"); + FLAG_DESCRIPTION_CUSTOM_INTERACT_ENTITY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-interact-entity"); + FLAG_DESCRIPTION_CUSTOM_INTERACT_INVENTORY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-interact-inventory"); + FLAG_DESCRIPTION_CUSTOM_INVINCIBLE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-invincible"); + FLAG_DESCRIPTION_CUSTOM_ITEM_DROP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-item-drop"); + FLAG_DESCRIPTION_CUSTOM_ITEM_PICKUP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-item-pickup"); + FLAG_DESCRIPTION_CUSTOM_MONSTER_DAMAGE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-monster-damage"); + FLAG_DESCRIPTION_CUSTOM_PISTONS = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-pistons"); + FLAG_DESCRIPTION_CUSTOM_PORTAL_USE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-portal-use"); + FLAG_DESCRIPTION_CUSTOM_SPAWN_AMBIENT = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-spawn-ambient"); + FLAG_DESCRIPTION_CUSTOM_SPAWN_ANIMAL = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-spawn-animal"); + FLAG_DESCRIPTION_CUSTOM_SPAWN_AQUATIC = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-spawn-aquatic"); + FLAG_DESCRIPTION_CUSTOM_SPAWN_MONSTER = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-spawn-monster"); + FLAG_DESCRIPTION_CUSTOM_TELEPORT_FROM = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-teleport-from"); + FLAG_DESCRIPTION_CUSTOM_TELEPORT_TO = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-teleport-to"); + FLAG_DESCRIPTION_CUSTOM_USE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-use"); + FLAG_DESCRIPTION_CUSTOM_VEHICLE_DESTROY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-vehicle-destroy"); + FLAG_DESCRIPTION_CUSTOM_WITHER_DAMAGE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-wither-damage"); + FLAG_DESCRIPTION_CUSTOM_BLOCK_TRAMPLING = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-block-trampling"); + FLAG_DESCRIPTION_CUSTOM_CHEST_ACCESS = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-chest-access"); + FLAG_DESCRIPTION_CUSTOM_CHORUS_FRUIT_TELEPORT = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-chorus-fruit-teleport"); + FLAG_DESCRIPTION_CUSTOM_CROP_GROWTH = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-crop-growth"); + FLAG_DESCRIPTION_CUSTOM_DAMAGE_ANIMALS = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-damage-animals"); + FLAG_DESCRIPTION_CUSTOM_ENDERMAN_GRIEF = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-enderman-grief"); + FLAG_DESCRIPTION_CUSTOM_ENTER_PLAYER = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-enter-player"); + FLAG_DESCRIPTION_CUSTOM_EXPLOSION_CREEPER = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-explosion-creeper"); + FLAG_DESCRIPTION_CUSTOM_EXPLOSION_TNT = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-explosion-tnt"); + FLAG_DESCRIPTION_CUSTOM_FIRE_DAMAGE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-fire-damage"); + FLAG_DESCRIPTION_CUSTOM_FIRE_SPREAD = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-fire-spread"); + FLAG_DESCRIPTION_CUSTOM_GRASS_GROWTH = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-grass-growth"); + FLAG_DESCRIPTION_CUSTOM_ICE_FORM = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-ice-form"); + FLAG_DESCRIPTION_CUSTOM_ICE_MELT = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-ice-melt"); + FLAG_DESCRIPTION_CUSTOM_LAVA_FLOW = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-lava-flow"); + FLAG_DESCRIPTION_CUSTOM_LEAF_DECAY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-leaf-decay"); + FLAG_DESCRIPTION_CUSTOM_LIGHTNING = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-lightning"); + FLAG_DESCRIPTION_CUSTOM_LIGHTER = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-lighter"); + FLAG_DESCRIPTION_CUSTOM_MUSHROOM_GROWTH = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-mushroom-growth"); + FLAG_DESCRIPTION_CUSTOM_MYCELIUM_SPREAD = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-mycelium-spread"); + FLAG_DESCRIPTION_CUSTOM_PVP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-pvp"); + FLAG_DESCRIPTION_CUSTOM_RIDE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-ride"); + FLAG_DESCRIPTION_CUSTOM_SLEEP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-sleep"); + FLAG_DESCRIPTION_CUSTOM_SNOW_FALL = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-snow-fall"); + FLAG_DESCRIPTION_CUSTOM_SNOW_MELT = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-snow-melt"); + FLAG_DESCRIPTION_CUSTOM_SNOWMAN_TRAIL = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-snowman-trail"); + FLAG_DESCRIPTION_CUSTOM_SOIL_DRY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-soil-dry"); + FLAG_DESCRIPTION_CUSTOM_VEHICLE_PLACE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-vehicle-place"); + FLAG_DESCRIPTION_CUSTOM_VINE_GROWTH = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-vine-growth"); + FLAG_DESCRIPTION_CUSTOM_WATER_FLOW = MessageStorage.MESSAGE_DATA.getMessage("flag-description-custom-water-flow"); + FLAG_DESCRIPTION_BLOCK_BREAK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-break"); + FLAG_DESCRIPTION_BLOCK_GROW = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-grow"); + FLAG_DESCRIPTION_BLOCK_MODIFY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-modify"); + FLAG_DESCRIPTION_BLOCK_PLACE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-place"); + FLAG_DESCRIPTION_BLOCK_SPREAD = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-spread"); + FLAG_DESCRIPTION_COLLIDE_BLOCK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-collide-block"); + FLAG_DESCRIPTION_COLLIDE_ENTITY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-collide-entity"); + FLAG_DESCRIPTION_COMMAND_EXECUTE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-command-execute"); + FLAG_DESCRIPTION_COMMAND_EXECUTE_PVP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-command-execute-pvp"); + FLAG_DESCRIPTION_ENTER_CLAIM = MessageStorage.MESSAGE_DATA.getMessage("flag-description-enter-claim"); + FLAG_DESCRIPTION_ENTITY_CHUNK_SPAWN = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-chunk-spawn"); + FLAG_DESCRIPTION_ENTITY_DAMAGE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-damage"); + FLAG_DESCRIPTION_ENTITY_RIDING = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-riding"); + FLAG_DESCRIPTION_ENTITY_SPAWN = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-spawn"); + FLAG_DESCRIPTION_ENTITY_TELEPORT_FROM = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-teleport-from"); + FLAG_DESCRIPTION_ENTITY_TELEPORT_TO = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-teleport-to"); + FLAG_DESCRIPTION_EXIT_CLAIM = MessageStorage.MESSAGE_DATA.getMessage("flag-description-exit-claim"); + FLAG_DESCRIPTION_EXPLOSION_BLOCK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-explosion-block"); + FLAG_DESCRIPTION_EXPLOSION_ENTITY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-explosion-entity"); + FLAG_DESCRIPTION_INTERACT_BLOCK_PRIMARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-primary"); + FLAG_DESCRIPTION_INTERACT_BLOCK_SECONDARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-block-secondary"); + FLAG_DESCRIPTION_INTERACT_ENTITY_PRIMARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-primary"); + FLAG_DESCRIPTION_INTERACT_ENTITY_SECONDARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-entity-secondary"); + FLAG_DESCRIPTION_INTERACT_ITEM_PRIMARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-interact-item-primary"); + FLAG_DESCRIPTION_INTERACT_ITEM_SECONDARY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-interact-item-secondary"); + FLAG_DESCRIPTION_INTERACT_INVENTORY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-interact-inventory"); + FLAG_DESCRIPTION_INTERACT_INVENTORY_CLICK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-interact-inventory-click"); + FLAG_DESCRIPTION_ITEM_DROP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-item-drop"); + FLAG_DESCRIPTION_ITEM_PICKUP = MessageStorage.MESSAGE_DATA.getMessage("flag-description-item-pickup"); + FLAG_DESCRIPTION_ITEM_SPAWN = MessageStorage.MESSAGE_DATA.getMessage("flag-description-item-spawn"); + FLAG_DESCRIPTION_ITEM_USE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-item-use"); + FLAG_DESCRIPTION_LEAF_DECAY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-leaf-decay"); + FLAG_DESCRIPTION_LIQUID_FLOW = MessageStorage.MESSAGE_DATA.getMessage("flag-description-liquid-flow"); + FLAG_DESCRIPTION_PORTAL_USE = MessageStorage.MESSAGE_DATA.getMessage("flag-description-portal-use"); + FLAG_DESCRIPTION_PROJECTILE_IMPACT_BLOCK = MessageStorage.MESSAGE_DATA.getMessage("flag-description-projectile-impact-block"); + FLAG_DESCRIPTION_PROJECTILE_IMPACT_ENTITY = MessageStorage.MESSAGE_DATA.getMessage("flag-description-projectile-impact-entity"); + FLAG_RESET_SUCCESS = MessageStorage.MESSAGE_DATA.getMessage("flag-reset-success"); + FLAG_RESET_WARNING = MessageStorage.MESSAGE_DATA.getMessage("flag-reset-warning"); + FLAG_UI_CLICK_ALLOW = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-click-allow"); + FLAG_UI_CLICK_DENY = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-click-deny"); + FLAG_UI_CLICK_REMOVE = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-click-remove"); + FLAG_UI_INFO_CLAIM = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-info-claim"); + FLAG_UI_INFO_DEFAULT = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-info-default"); + FLAG_UI_INFO_INHERIT = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-info-inherit"); + FLAG_UI_INFO_OVERRIDE = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-info-override"); + FLAG_UI_OVERRIDE_NO_PERMISSION = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-override-no-permission"); + FLAG_UI_RETURN_FLAGS = MessageStorage.MESSAGE_DATA.getMessage("flag-ui-return-flags"); + LABEL_ACCESSORS = MessageStorage.MESSAGE_DATA.getMessage("label-accessors"); + LABEL_AREA = MessageStorage.MESSAGE_DATA.getMessage("label-area"); + LABEL_BLOCKS = MessageStorage.MESSAGE_DATA.getMessage("label-blocks"); + LABEL_BUILDERS = MessageStorage.MESSAGE_DATA.getMessage("label-builders"); + LABEL_BUY = MessageStorage.MESSAGE_DATA.getMessage("label-buy"); + LABEL_CHILDREN = MessageStorage.MESSAGE_DATA.getMessage("label-children"); + LABEL_CONFIRM = MessageStorage.MESSAGE_DATA.getMessage("label-confirm"); + LABEL_CONTAINERS = MessageStorage.MESSAGE_DATA.getMessage("label-containers"); + LABEL_CONTEXT = MessageStorage.MESSAGE_DATA.getMessage("label-context"); + LABEL_CREATED = MessageStorage.MESSAGE_DATA.getMessage("label-created"); + LABEL_DISPLAYING = MessageStorage.MESSAGE_DATA.getMessage("label-displaying"); + LABEL_EXPIRED = MessageStorage.MESSAGE_DATA.getMessage("label-expired"); + LABEL_FAREWELL = MessageStorage.MESSAGE_DATA.getMessage("label-farewell"); + LABEL_FLAG = MessageStorage.MESSAGE_DATA.getMessage("label-flag"); + LABEL_GREETING = MessageStorage.MESSAGE_DATA.getMessage("label-greeting"); + LABEL_GROUP = MessageStorage.MESSAGE_DATA.getMessage("label-group"); + LABEL_INHERIT = MessageStorage.MESSAGE_DATA.getMessage("label-inherit"); + LABEL_LOCATION = MessageStorage.MESSAGE_DATA.getMessage("label-location"); + LABEL_MANAGERS = MessageStorage.MESSAGE_DATA.getMessage("label-managers"); + LABEL_NAME = MessageStorage.MESSAGE_DATA.getMessage("label-name"); + LABEL_NO = MessageStorage.MESSAGE_DATA.getMessage("label-no"); + LABEL_OUTPUT = MessageStorage.MESSAGE_DATA.getMessage("label-output"); + LABEL_OWNER = MessageStorage.MESSAGE_DATA.getMessage("label-owner"); + LABEL_PERMISSION = MessageStorage.MESSAGE_DATA.getMessage("label-permission"); + LABEL_PLAYER = MessageStorage.MESSAGE_DATA.getMessage("label-player"); + LABEL_PRICE = MessageStorage.MESSAGE_DATA.getMessage("label-price"); + LABEL_RAID = MessageStorage.MESSAGE_DATA.getMessage("label-raid"); + LABEL_RESIZABLE = MessageStorage.MESSAGE_DATA.getMessage("label-resizable"); + LABEL_RESULT = MessageStorage.MESSAGE_DATA.getMessage("label-result"); + LABEL_SCHEMATIC = MessageStorage.MESSAGE_DATA.getMessage("label-schematic"); + LABEL_SOURCE = MessageStorage.MESSAGE_DATA.getMessage("label-source"); + LABEL_SPAWN = MessageStorage.MESSAGE_DATA.getMessage("label-spawn"); + LABEL_TARGET = MessageStorage.MESSAGE_DATA.getMessage("label-target"); + LABEL_TRUST = MessageStorage.MESSAGE_DATA.getMessage("label-trust"); + LABEL_TYPE = MessageStorage.MESSAGE_DATA.getMessage("label-type"); + LABEL_UNKNOWN = MessageStorage.MESSAGE_DATA.getMessage("label-unknown"); + LABEL_USER = MessageStorage.MESSAGE_DATA.getMessage("label-user"); + LABEL_WORLD = MessageStorage.MESSAGE_DATA.getMessage("label-world"); + LABEL_YES = MessageStorage.MESSAGE_DATA.getMessage("label-yes"); + MODE_ADMIN = MessageStorage.MESSAGE_DATA.getMessage("mode-admin"); + MODE_BASIC = MessageStorage.MESSAGE_DATA.getMessage("mode-basic"); + MODE_NATURE = MessageStorage.MESSAGE_DATA.getMessage("mode-nature"); + MODE_SUBDIVISION = MessageStorage.MESSAGE_DATA.getMessage("mode-subdivision"); + MODE_TOWN = MessageStorage.MESSAGE_DATA.getMessage("mode-town"); + OPTION_DESCRIPTION_ABANDON_DELAY = MessageStorage.MESSAGE_DATA.getMessage("option-description-abandon-delay"); + OPTION_DESCRIPTION_ABANDON_RETURN_RATIO = MessageStorage.MESSAGE_DATA.getMessage("option-description-abandon-return-ratio"); + OPTION_DESCRIPTION_BLOCKS_ACCRUED_PER_HOUR = MessageStorage.MESSAGE_DATA.getMessage("option-description-blocks-accrued-per-hour"); + OPTION_DESCRIPTION_CHEST_EXPIRATION = MessageStorage.MESSAGE_DATA.getMessage("option-description-chest-expiration"); + OPTION_DESCRIPTION_CREATE_LIMIT = MessageStorage.MESSAGE_DATA.getMessage("option-description-create-limit"); + OPTION_DESCRIPTION_CREATE_MODE = MessageStorage.MESSAGE_DATA.getMessage("option-description-create-mode"); + OPTION_DESCRIPTION_ECONOMY_BLOCK_COST = MessageStorage.MESSAGE_DATA.getMessage("option-description-economy-block-cost"); + OPTION_DESCRIPTION_ECONOMY_BLOCK_SELL_RETURN = MessageStorage.MESSAGE_DATA.getMessage("option-description-economy-block-sell-return"); + OPTION_DESCRIPTION_EXPIRATION = MessageStorage.MESSAGE_DATA.getMessage("option-description-expiration"); + OPTION_DESCRIPTION_INITIAL_BLOCKS = MessageStorage.MESSAGE_DATA.getMessage("option-description-initial-blocks"); + OPTION_DESCRIPTION_MAX_ACCRUED_BLOCKS = MessageStorage.MESSAGE_DATA.getMessage("option-description-max-accrued-blocks"); + OPTION_DESCRIPTION_MAX_LEVEL = MessageStorage.MESSAGE_DATA.getMessage("option-description-max-level"); + OPTION_DESCRIPTION_MAX_SIZE_X = MessageStorage.MESSAGE_DATA.getMessage("option-description-max-size-x"); + OPTION_DESCRIPTION_MAX_SIZE_Y = MessageStorage.MESSAGE_DATA.getMessage("option-description-max-size-y"); + OPTION_DESCRIPTION_MAX_SIZE_Z = MessageStorage.MESSAGE_DATA.getMessage("option-description-max-size-z"); + OPTION_DESCRIPTION_MIN_LEVEL = MessageStorage.MESSAGE_DATA.getMessage("option-description-min-level"); + OPTION_DESCRIPTION_MIN_SIZE_X = MessageStorage.MESSAGE_DATA.getMessage("option-description-min-size-x"); + OPTION_DESCRIPTION_MIN_SIZE_Y = MessageStorage.MESSAGE_DATA.getMessage("option-description-min-size-y"); + OPTION_DESCRIPTION_MIN_SIZE_Z = MessageStorage.MESSAGE_DATA.getMessage("option-description-min-size-z"); + OPTION_DESCRIPTION_PLAYER_COMMAND = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-command"); + OPTION_DESCRIPTION_PLAYER_DENY_FLIGHT = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-deny-flight"); + OPTION_DESCRIPTION_PLAYER_DENY_GODMODE = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-deny-godmode"); + OPTION_DESCRIPTION_PLAYER_DENY_HUNGER = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-deny-hunger"); + OPTION_DESCRIPTION_PLAYER_GAMEMODE = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-gamemode"); + OPTION_DESCRIPTION_PLAYER_HEALTH_REGEN = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-health-regen"); + OPTION_DESCRIPTION_PLAYER_KEEP_INVENTORY = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-keep-inventory"); + OPTION_DESCRIPTION_PLAYER_KEEP_LEVEL = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-keep-level"); + OPTION_DESCRIPTION_PLAYER_WALK_SPEED = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-walk-speed"); + OPTION_DESCRIPTION_PLAYER_WEATHER = MessageStorage.MESSAGE_DATA.getMessage("option-description-player-weather"); + OPTION_DESCRIPTION_RADIUS_LIST = MessageStorage.MESSAGE_DATA.getMessage("option-description-radius-list"); + OPTION_DESCRIPTION_RADIUS_INSPECT = MessageStorage.MESSAGE_DATA.getMessage("option-description-radius-inspect"); + OPTION_DESCRIPTION_TAX_EXPIRATION = MessageStorage.MESSAGE_DATA.getMessage("option-description-tax-expiration"); + OPTION_DESCRIPTION_TAX_EXPIRATION_DAYS_KEEP = MessageStorage.MESSAGE_DATA.getMessage("option-description-tax-expiration-days-keep"); + OPTION_DESCRIPTION_TAX_RATE = MessageStorage.MESSAGE_DATA.getMessage("option-description-tax-rate"); + OPTION_PLAYER_DENY_FLIGHT = MessageStorage.MESSAGE_DATA.getMessage("option-player-deny-flight"); + OWNER_ADMIN = MessageStorage.MESSAGE_DATA.getMessage("owner-admin"); + PERMISSION_ASSIGN_WITHOUT_HAVING = MessageStorage.MESSAGE_DATA.getMessage("permission-assign-without-having"); + PERMISSION_CLAIM_CREATE = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-create"); + PERMISSION_CLAIM_ENTER = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-enter"); + PERMISSION_CLAIM_EXIT = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-exit"); + PERMISSION_CLAIM_LIST = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-list"); + PERMISSION_CLAIM_RESET_FLAGS_SELF = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-reset-flags-self"); + PERMISSION_CLAIM_RESIZE = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-resize"); + PERMISSION_CLAIM_SALE = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-sale"); + PERMISSION_CLAIM_TRANSFER_ADMIN = MessageStorage.MESSAGE_DATA.getMessage("permission-claim-transfer-admin"); + PERMISSION_CLEAR = MessageStorage.MESSAGE_DATA.getMessage("permission-clear"); + PERMISSION_CLEAR_ALL = MessageStorage.MESSAGE_DATA.getMessage("permission-clear-all"); + PERMISSION_COMMAND_TRUST = MessageStorage.MESSAGE_DATA.getMessage("permission-command-trust"); + PERMISSION_CUBOID = MessageStorage.MESSAGE_DATA.getMessage("permission-cuboid"); + PERMISSION_EDIT_CLAIM = MessageStorage.MESSAGE_DATA.getMessage("permission-edit-claim"); + PERMISSION_FIRE_SPREAD = MessageStorage.MESSAGE_DATA.getMessage("permission-fire-spread"); + PERMISSION_FLAG_DEFAULTS = MessageStorage.MESSAGE_DATA.getMessage("permission-flag-defaults"); + PERMISSION_FLAG_OVERRIDES = MessageStorage.MESSAGE_DATA.getMessage("permission-flag-overrides"); + PERMISSION_FLAG_USE = MessageStorage.MESSAGE_DATA.getMessage("permission-flag-use"); + PERMISSION_FLOW_LIQUID = MessageStorage.MESSAGE_DATA.getMessage("permission-flow-liquid"); + PERMISSION_GLOBAL_OPTION = MessageStorage.MESSAGE_DATA.getMessage("permission-global-option"); + PERMISSION_GRANT = MessageStorage.MESSAGE_DATA.getMessage("permission-grant"); + PERMISSION_GROUP_OPTION = MessageStorage.MESSAGE_DATA.getMessage("permission-group-option"); + PERMISSION_OPTION_DEFAULTS = MessageStorage.MESSAGE_DATA.getMessage("permission-option-defaults"); + PERMISSION_OPTION_OVERRIDES = MessageStorage.MESSAGE_DATA.getMessage("permission-option-overrides"); + PERMISSION_OPTION_USE = MessageStorage.MESSAGE_DATA.getMessage("permission-option-use"); + PERMISSION_OVERRIDE_DENY = MessageStorage.MESSAGE_DATA.getMessage("permission-override-deny"); + PERMISSION_PLAYER_ADMIN_FLAGS = MessageStorage.MESSAGE_DATA.getMessage("permission-player-admin-flags"); + PERMISSION_PLAYER_OPTION = MessageStorage.MESSAGE_DATA.getMessage("permission-player-option"); + PERMISSION_PLAYER_VIEW_OTHERS = MessageStorage.MESSAGE_DATA.getMessage("permission-player-view-others"); + PERMISSION_VISUAL_CLAIMS_NEARBY = MessageStorage.MESSAGE_DATA.getMessage("permission-visual-claims-nearby"); + PLAYERINFO_UI_TITLE = MessageStorage.MESSAGE_DATA.getMessage("playerinfo-ui-title"); + PLUGIN_EVENT_CANCEL = MessageStorage.MESSAGE_DATA.getMessage("plugin-event-cancel"); + PLUGIN_RELOAD = MessageStorage.MESSAGE_DATA.getMessage("plugin-reload"); + PVP_CLAIM_NOT_ALLOWED = MessageStorage.MESSAGE_DATA.getMessage("pvp-claim-not-allowed"); + PVP_SOURCE_NOT_ALLOWED = MessageStorage.MESSAGE_DATA.getMessage("pvp-source-not-allowed"); + PVP_TARGET_NOT_ALLOWED = MessageStorage.MESSAGE_DATA.getMessage("pvp-target-not-allowed"); + RESIZE_OVERLAP = MessageStorage.MESSAGE_DATA.getMessage("resize-overlap"); + RESIZE_OVERLAP_SUBDIVISION = MessageStorage.MESSAGE_DATA.getMessage("resize-overlap-subdivision"); + RESIZE_SAME_LOCATION = MessageStorage.MESSAGE_DATA.getMessage("resize-same-location"); + RESIZE_START = MessageStorage.MESSAGE_DATA.getMessage("resize-start"); + SCHEMATIC_ABANDON_ALL_RESTORE_WARNING = MessageStorage.MESSAGE_DATA.getMessage("schematic-abandon-all-restore-warning"); + SCHEMATIC_ABANDON_RESTORE_WARNING = MessageStorage.MESSAGE_DATA.getMessage("schematic-abandon-restore-warning"); + SCHEMATIC_CREATE = MessageStorage.MESSAGE_DATA.getMessage("schematic-create"); + SCHEMATIC_CREATE_COMPLETE = MessageStorage.MESSAGE_DATA.getMessage("schematic-create-complete"); + SCHEMATIC_CREATE_FAIL = MessageStorage.MESSAGE_DATA.getMessage("schematic-create-fail"); + SCHEMATIC_NONE = MessageStorage.MESSAGE_DATA.getMessage("schematic-none"); + SPAWN_NOT_SET = MessageStorage.MESSAGE_DATA.getMessage("spawn-not-set"); + TELEPORT_MOVE_CANCEL = MessageStorage.MESSAGE_DATA.getMessage("teleport-move-cancel"); + TELEPORT_NO_SAFE_LOCATION = MessageStorage.MESSAGE_DATA.getMessage("teleport-no-safe-location"); + TITLE_ACCESSOR = MessageStorage.MESSAGE_DATA.getMessage("title-accessor"); + TITLE_ALL = MessageStorage.MESSAGE_DATA.getMessage("title-all"); + TITLE_BUILDER = MessageStorage.MESSAGE_DATA.getMessage("title-builder"); + TITLE_CLAIM = MessageStorage.MESSAGE_DATA.getMessage("title-claim"); + TITLE_CONTAINER = MessageStorage.MESSAGE_DATA.getMessage("title-container"); + TITLE_DEFAULT = MessageStorage.MESSAGE_DATA.getMessage("title-default"); + TITLE_INHERIT = MessageStorage.MESSAGE_DATA.getMessage("title-inherit"); + TITLE_MANAGER = MessageStorage.MESSAGE_DATA.getMessage("title-manager"); + TITLE_OWN = MessageStorage.MESSAGE_DATA.getMessage("title-own"); + TITLE_OVERRIDE = MessageStorage.MESSAGE_DATA.getMessage("title-override"); + TOWN_CHAT_DISABLED = MessageStorage.MESSAGE_DATA.getMessage("town-chat-disabled"); + TOWN_CHAT_ENABLED = MessageStorage.MESSAGE_DATA.getMessage("town-chat-enabled"); + TOWN_NOT_FOUND = MessageStorage.MESSAGE_DATA.getMessage("town-not-found"); + TOWN_NOT_IN = MessageStorage.MESSAGE_DATA.getMessage("town-not-in"); + TOWN_OWNER = MessageStorage.MESSAGE_DATA.getMessage("town-owner"); + TOWN_TAG_CLEAR = MessageStorage.MESSAGE_DATA.getMessage("town-tag-clear"); + TOWN_TAX_NO_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("town-tax-no-claims"); + TRUST_CLICK_SHOW_LIST = MessageStorage.MESSAGE_DATA.getMessage("trust-click-show-list"); + TRUST_INVALID = MessageStorage.MESSAGE_DATA.getMessage("trust-invalid"); + TRUST_LIST_HEADER = MessageStorage.MESSAGE_DATA.getMessage("trust-list-header"); + TRUST_NO_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("trust-no-claims"); + TRUST_SELF = MessageStorage.MESSAGE_DATA.getMessage("trust-self"); + UI_CLICK_CONFIRM = MessageStorage.MESSAGE_DATA.getMessage("ui-click-confirm"); + UNTRUST_NO_CLAIMS = MessageStorage.MESSAGE_DATA.getMessage("untrust-no-claims"); + UNTRUST_SELF = MessageStorage.MESSAGE_DATA.getMessage("untrust-self"); + + // reload Flag/Option caches + for (Option option : OptionRegistryModule.getInstance().getAll()) { + ((GDOption) option).reloadDescription(); + } + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + ((GDFlag) flag).reloadDescription(); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/cache/PermissionHolderCache.java b/sponge/src/main/java/com/griefdefender/cache/PermissionHolderCache.java new file mode 100644 index 0000000..a74f417 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/cache/PermissionHolderCache.java @@ -0,0 +1,154 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.cache; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.permission.GDPermissionGroup; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.util.PermissionUtil; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.service.user.UserStorageService; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +public class PermissionHolderCache { + + private static PermissionHolderCache instance; + private final Cache<UUID, GDPermissionUser> userCache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + private final Cache<String, GDPermissionGroup> groupCache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + private final ConcurrentHashMap<GDPermissionHolder, Cache<Integer, Tristate>> permissionCache = new ConcurrentHashMap<>(); + + public GDPermissionUser getOrCreateUser(User user) { + if (user == null) { + return null; + } + + return this.getOrCreateUser(user.getUniqueId()); + } + + public GDPermissionUser getOrCreateUser(UUID uuid) { + if (uuid == null) { + return null; + } + if (uuid.equals(GriefDefenderPlugin.PUBLIC_UUID)) { + return GriefDefenderPlugin.PUBLIC_USER; + } + if (uuid.equals(GriefDefenderPlugin.WORLD_USER_UUID)) { + return GriefDefenderPlugin.WORLD_USER; + } + + GDPermissionUser holder = this.userCache.getIfPresent(uuid); + if (holder != null) { + return holder; + } + + holder = new GDPermissionUser(uuid); + this.userCache.put(uuid, holder); + return holder; + } + + public GDPermissionUser getOrCreateUser(String username) { + if (username == null) { + return null; + } + + final UUID uuid = PermissionUtil.getInstance().lookupUserUniqueId(username); + if (uuid != null) { + return this.getOrCreateUser(uuid); + } + User user = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(username).orElse(null); + if (user == null) { + user = NMSUtil.getInstance().createUserFromCache(username); + } + if (user != null) { + return this.getOrCreateUser(user); + } + + return null; + } + + public GDPermissionGroup getOrCreateGroup(String groupName) { + if (groupName == null) { + return null; + } + GDPermissionGroup holder = this.groupCache.getIfPresent(groupName); + if (holder != null) { + return holder; + } + + holder = new GDPermissionGroup(groupName); + this.groupCache.put(groupName, holder); + return holder; + } + + public GDPermissionHolder getOrCreateHolder(String identifier) { + if (identifier == null) { + return null; + } + UUID uuid = null; + try { + uuid = UUID.fromString(identifier); + } catch (IllegalArgumentException e) { + return this.getOrCreateGroup(identifier); + } + + return this.getOrCreateUser(uuid); + } + + public Cache<Integer, Tristate> getOrCreatePermissionCache(GDPermissionHolder holder) { + Cache<Integer, Tristate> cache = this.permissionCache.get(holder); + if (cache == null) { + cache = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(); + this.permissionCache.put(holder, cache); + } + return cache; + } + + public void invalidateAllPermissionCache() { + for (Cache<Integer, Tristate> cache : this.permissionCache.values()) { + cache.invalidateAll(); + } + } + + static { + instance = new PermissionHolderCache(); + } + + public static PermissionHolderCache getInstance() { + return instance; + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/ClaimContextCalculator.java b/sponge/src/main/java/com/griefdefender/claim/ClaimContextCalculator.java new file mode 100644 index 0000000..632cb77 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/ClaimContextCalculator.java @@ -0,0 +1,87 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.context.Context; +import org.spongepowered.api.service.context.ContextCalculator; +import org.spongepowered.api.service.permission.Subject; + +import java.util.Set; +import java.util.UUID; + +public class ClaimContextCalculator implements ContextCalculator<Subject> { + + @Override + public void accumulateContexts(Subject calculable, Set<Context> accumulator) { + if (calculable.getCommandSource().isPresent() && calculable.getCommandSource().get() instanceof Player) { + Player player = (Player) calculable.getCommandSource().get(); + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(player.getWorld(), player.getUniqueId()); + if (playerData == null) { + return; + } + if (playerData.ignoreActiveContexts) { + playerData.ignoreActiveContexts = false; + return; + } + + GDClaim sourceClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (sourceClaim != null) { + if (playerData == null || playerData.canIgnoreClaim(sourceClaim)) { + return; + } + + if (sourceClaim.parent != null && sourceClaim.getData().doesInheritParent()) { + accumulator.add(sourceClaim.parent.getSpongeContext()); + } else { + accumulator.add(sourceClaim.getSpongeContext()); + } + } + } + + } + + @Override + public boolean matches(Context context, Subject subject) { + if (context.equals("gd_claim")) { + if (subject.getCommandSource().isPresent() && subject.getCommandSource().get() instanceof Player) { + Player player = (Player) subject.getCommandSource().get(); + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(player.getWorld(), player.getUniqueId()); + if (playerData == null) { + return false; + } + + GDClaim playerClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (playerClaim != null && playerClaim.getUniqueId().equals(UUID.fromString(context.getValue()))) { + return true; + } + } + } + + return false; + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDClaim.java b/sponge/src/main/java/com/griefdefender/claim/GDClaim.java new file mode 100644 index 0000000..0982268 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDClaim.java @@ -0,0 +1,3111 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.claim.ClaimManager; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimSchematic; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.data.ClaimData; +import com.griefdefender.api.event.EventCause; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.configuration.ClaimDataConfig; +import com.griefdefender.configuration.ClaimStorageData; +import com.griefdefender.configuration.IClaimData; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.TownDataConfig; +import com.griefdefender.configuration.TownStorageData; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDChangeClaimEvent; +import com.griefdefender.event.GDCreateClaimEvent; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.event.GDGroupTrustClaimEvent; +import com.griefdefender.event.GDSaveClaimEvent; +import com.griefdefender.event.GDTransferClaimEvent; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.registry.TrustTypeRegistryModule; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.storage.FileStorage; +import com.griefdefender.util.EconomyUtil; +import com.griefdefender.util.PermissionUtil; +import com.griefdefender.util.SpongeContexts; +import com.griefdefender.util.SpongeUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; +import org.spongepowered.api.world.Chunk; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +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.Optional; +import java.util.Set; +import java.util.UUID; + +public class GDClaim implements Claim { + + public static final BaseStorage DATASTORE = GriefDefenderPlugin.getInstance().dataStore; + // Note: 2D cuboids will ignore the upper Y value while 3D cuboids do not + public Vector3i lesserBoundaryCorner; + public Vector3i greaterBoundaryCorner; + private World world; + private ClaimType type = ClaimTypes.BASIC; + private Set<Long> chunkHashes; + private final int hashCode; + private final GDClaimManager worldClaimManager; + private final Claim wildernessClaim; + + // Permission Context + private final Context context; + private final Context overrideClaimContext; + private final org.spongepowered.api.service.context.Context spongeContext; + private final org.spongepowered.api.service.context.Context spongeOverrideClaimContext; + + private UUID id = null; + private UUID ownerUniqueId; + public boolean cuboid = false; + public boolean markVisualDirty = false; + + protected ClaimStorageData claimStorage; + protected IClaimData claimData; + + public GDClaim parent = null; + public Set<Claim> children = new HashSet<>(); + public ClaimVisual claimVisual; + public List<UUID> playersWatching = new ArrayList<>(); + public Map<String, ClaimSchematic> schematics = new HashMap<>(); + + private GDPlayerData ownerPlayerData; + private Account economyAccount; + private static final int MAX_AREA = GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME ? 2560000 : 10000; + + public GDClaim(World world, Vector3i point1, Vector3i point2, ClaimType type, UUID ownerUniqueId, boolean cuboid) { + this(world, point1, point2, type, ownerUniqueId, cuboid, null); + } + + public GDClaim(World world, Vector3i point1, Vector3i point2, ClaimType type, UUID ownerUniqueId, boolean cuboid, GDClaim parent) { + int minx = Math.min(point1.getX(), point2.getX()); + int miny = Math.min(point1.getY(), point2.getY()); + int minz = Math.min(point1.getZ(), point2.getZ()); + int maxx = Math.max(point1.getX(), point2.getX()); + int maxy = Math.max(point1.getY(), point2.getY()); + int maxz = Math.max(point1.getZ(), point2.getZ()); + + this.world = world; + this.lesserBoundaryCorner = new Vector3i(minx, miny, minz); + this.greaterBoundaryCorner = new Vector3i(maxx, maxy, maxz); + if (ownerUniqueId != null) { + this.ownerUniqueId = ownerUniqueId; + this.ownerPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.world.getUniqueId(), this.ownerUniqueId); + } + this.type = type; + this.id = UUID.randomUUID(); + this.context = new Context("gd_claim", this.id.toString()); + this.overrideClaimContext = new Context("gd_claim_override", this.id.toString()); + this.spongeContext = SpongeUtil.getSpongeContext(this.context); + this.spongeOverrideClaimContext = SpongeUtil.getSpongeContext(this.overrideClaimContext); + this.cuboid = cuboid; + this.parent = parent; + this.hashCode = this.id.hashCode(); + this.worldClaimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + if (this.type == ClaimTypes.WILDERNESS) { + this.wildernessClaim = this; + } else { + this.wildernessClaim = this.worldClaimManager.getWildernessClaim(); + } + } + + // Used for visualizations + public GDClaim(World world, Vector3i lesserBoundaryCorner, Vector3i greaterBoundaryCorner, ClaimType type, boolean cuboid) { + this(world, lesserBoundaryCorner, greaterBoundaryCorner, UUID.randomUUID(), type, null, cuboid); + } + + // Used at server startup + public GDClaim(World world, Vector3i lesserBoundaryCorner, Vector3i greaterBoundaryCorner, UUID claimId, ClaimType type, UUID ownerUniqueId, boolean cuboid) { + this.id = claimId; + this.overrideClaimContext = new Context("gd_claim_override", this.id.toString()); + this.lesserBoundaryCorner = lesserBoundaryCorner; + this.greaterBoundaryCorner = greaterBoundaryCorner; + this.world = world; + if (ownerUniqueId != null) { + this.ownerUniqueId = ownerUniqueId; + this.ownerPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.world.getUniqueId(), this.ownerUniqueId); + } + this.type = type; + this.cuboid = cuboid; + this.context = new Context("gd_claim", this.id.toString()); + this.spongeContext = SpongeUtil.getSpongeContext(this.context); + this.spongeOverrideClaimContext = SpongeUtil.getSpongeContext(this.overrideClaimContext); + this.hashCode = this.id.hashCode(); + this.worldClaimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + if (this.type == ClaimTypes.WILDERNESS) { + this.wildernessClaim = this; + } else { + this.wildernessClaim = this.worldClaimManager.getWildernessClaim(); + } + } + + public void initializeClaimData(GDClaim parent) { + Path claimDataFolderPath = null; + // check if main world + if (parent != null) { + claimDataFolderPath = parent.getClaimStorage().filePath.getParent().resolve(this.type.getName().toLowerCase()); + } else { + claimDataFolderPath = BaseStorage.worldConfigMap.get(this.world.getUniqueId()).getPath().getParent().resolve("ClaimData").resolve(this.type.getName().toLowerCase()); + } + try { + if (Files.notExists(claimDataFolderPath)) { + Files.createDirectories(claimDataFolderPath); + } + } catch (IOException e) { + e.printStackTrace(); + } + File claimFile = new File(claimDataFolderPath + File.separator + this.id); + if (this.isTown()) { + this.claimStorage = new TownStorageData(claimFile.toPath(), this.world.getUniqueId(), this.ownerUniqueId, this.cuboid); + } else { + this.claimStorage = new ClaimStorageData(claimFile.toPath(), this.world.getUniqueId(), this.ownerUniqueId, this.type, this.cuboid); + } + this.claimData = this.claimStorage.getConfig(); + this.parent = parent; + if (parent != null) { + this.claimStorage.getConfig().setParent(parent.getUniqueId()); + } + this.updateClaimStorageData(); + } + + public ClaimType getType() { + return this.type; + } + + public void setType(ClaimType type) { + this.type = type; + if (this.claimData != null) { + this.claimData.setType(type); + } + } + + public ClaimVisual getVisualizer() { + if (this.claimVisual == null || this.markVisualDirty) { + this.claimVisual = new ClaimVisual(this, ClaimVisual.getClaimVisualType(this)); + this.markVisualDirty = false; + } + return this.claimVisual; + } + + public void resetVisuals() { + List<UUID> playersWatching = new ArrayList<>(this.playersWatching); + for (UUID playerUniqueId : playersWatching) { + final Player spongePlayer = Sponge.getServer().getPlayer(playerUniqueId).orElse(null); + final GDPlayerData data = this.worldClaimManager.getOrCreatePlayerData(playerUniqueId); + if (spongePlayer != null) { + data.revertActiveVisual(spongePlayer); + } + } + this.claimVisual = null; + } + + public GDPlayerData getOwnerPlayerData() { + if (this.ownerPlayerData == null && this.ownerUniqueId != null) { + this.ownerPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.world.getUniqueId(), this.ownerUniqueId); + } + + return this.ownerPlayerData; + } + + public UUID getOwnerUniqueId() { + if (this.isAdminClaim()) { + return GriefDefenderPlugin.ADMIN_USER_UUID; + } + if (this.ownerUniqueId == null) { + if (this.parent != null) { + return this.parent.getOwnerUniqueId(); + } + + return GriefDefenderPlugin.ADMIN_USER_UUID; + } + + return this.ownerUniqueId; + } + + public void setOwnerUniqueId(UUID uniqueId) { + this.ownerUniqueId = uniqueId; + } + + public boolean isAdminClaim() { + return this.type == ClaimTypes.ADMIN; + } + + @Override + public boolean isCuboid() { + if (this.claimData != null) { + return this.claimData.isCuboid(); + } + + return this.cuboid; + } + + @Override + public boolean isInTown() { + if (this.isTown()) { + return true; + } + + GDClaim parent = this.parent; + while (parent != null) { + if (parent.isTown()) { + return true; + } + parent = parent.parent; + } + + return false; + } + + @Override + public Optional<Claim> getTown() { + return Optional.ofNullable(this.getTownClaim()); + } + + @Nullable + public GDClaim getTownClaim() { + if (this.isTown()) { + return this; + } + + if (this.parent == null) { + return null; + } + + GDClaim parent = this.parent; + while (parent != null) { + if (parent.isTown()) { + return parent; + } + parent = parent.parent; + } + + return null; + } + + @Override + public UUID getUniqueId() { + return this.id; + } + + public Optional<Component> getName() { + if (this.claimData == null) { + return Optional.empty(); + } + return this.claimData.getName(); + } + + public Component getFriendlyNameType() { + return this.getFriendlyNameType(false); + } + + public Component getFriendlyNameType(boolean upper) { + if (this.type == ClaimTypes.ADMIN) { + if (upper) { + return TextComponent.of(this.type.getName().toUpperCase(), TextColor.RED); + } + return TextComponent.of("Admin", TextColor.RED); + } + + if (this.type == ClaimTypes.BASIC) { + if (upper) { + return TextComponent.of(this.type.getName().toUpperCase(), TextColor.YELLOW); + } + return TextComponent.of("Basic", TextColor.YELLOW); + } + + if (this.type == ClaimTypes.SUBDIVISION) { + if (upper) { + return TextComponent.of(this.type.getName().toUpperCase(), TextColor.AQUA); + } + return TextComponent.of("Subdivision", TextColor.AQUA); + } + + if (upper) { + return TextComponent.of(this.type.getName().toUpperCase(), TextColor.GREEN); + } + return TextComponent.of("Town", TextColor.GREEN); + } + + @Override + public int getClaimBlocks() { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + return this.getVolume(); + } + + return this.getArea(); + } + + @Override + public int getArea() { + final int claimWidth = this.greaterBoundaryCorner.getX() - this.lesserBoundaryCorner.getX() + 1; + final int claimLength = this.greaterBoundaryCorner.getZ() - this.lesserBoundaryCorner.getZ() + 1; + + return claimWidth * claimLength; + } + + @Override + public int getVolume() { + final int claimWidth = this.greaterBoundaryCorner.getX() - this.lesserBoundaryCorner.getX() + 1; + final int claimLength = this.greaterBoundaryCorner.getZ() - this.lesserBoundaryCorner.getZ() + 1; + final int claimHeight = this.greaterBoundaryCorner.getY() - this.lesserBoundaryCorner.getY() + 1; + + return claimWidth * claimLength * claimHeight; + } + + @Override + public int getWidth() { + return this.greaterBoundaryCorner.getX() - this.lesserBoundaryCorner.getX() + 1; + } + + @Override + public int getHeight() { + return this.greaterBoundaryCorner.getY() - this.lesserBoundaryCorner.getY() + 1; + } + + @Override + public int getLength() { + return this.greaterBoundaryCorner.getZ() - this.lesserBoundaryCorner.getZ() + 1; + } + + public Component allowEdit(Player player) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + if (user != null) { + return allowEdit(user); + } + + return TextComponent.of(""); + } + + public Component allowEdit(GDPermissionUser holder) { + return allowEdit(holder, false); + } + + public Component allowEdit(GDPermissionUser holder, boolean forced) { + if (this.isUserTrusted(holder, TrustTypes.MANAGER, null, forced)) { + return null; + } + + if (PermissionUtil.getInstance().holderHasPermission(holder, GDPermissions.COMMAND_DELETE_CLAIMS)) { + return null; + } + + if (this.parent != null && this.getData().doesInheritParent()) { + return this.parent.allowEdit(holder); + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.getWorldUniqueId(), holder.getUniqueId()); + if (playerData.canIgnoreClaim(this)) { + return null; + } + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_OWNER_ONLY, ImmutableMap.of( + "player", this.getOwnerName())); + return message; + } + + public Component allowGrantPermission(Player player) { + if(this.allowEdit(player) == null) { + return null; + } + + for(int i = 0; i < this.claimData.getManagers().size(); i++) { + UUID managerID = this.claimData.getManagers().get(i); + if(player.getUniqueId().equals(managerID)) { + return null; + } + } + + if(this.parent != null && this.getData().doesInheritParent()) { + return this.parent.allowGrantPermission(player); + } + + final Component reason = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, ImmutableMap.of( + "player", this.getOwnerName())); + return reason; + } + + @Override + public Vector3i getLesserBoundaryCorner() { + return this.lesserBoundaryCorner.clone(); + } + + @Override + public Vector3i getGreaterBoundaryCorner() { + return this.greaterBoundaryCorner.clone(); + } + + @Override + public Component getOwnerName() { + if (this.isAdminClaim() || this.isWilderness()) { + return MessageCache.getInstance().OWNER_ADMIN; + } + + if (this.getOwnerPlayerData() == null) { + return TextComponent.of("[unknown]"); + } + + return TextComponent.of(this.getOwnerPlayerData().getPlayerName()); + } + + @Override + public boolean contains(Vector3i pos, boolean excludeChildren) { + return this.contains(pos.getX(), pos.getY(), pos.getZ(), excludeChildren, null, false); + } + + public boolean contains(Location<World> location, boolean useBorderBlockRadius) { + return this.contains(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false, null, useBorderBlockRadius); + } + + public boolean contains(Location<World> location, GDPlayerData playerData, boolean useBorderBlockRadius) { + return this.contains(location.getBlockX(), location.getBlockY(), location.getBlockZ(), false, playerData, useBorderBlockRadius); + } + + public boolean contains(int x, int y, int z, boolean excludeChildren, GDPlayerData playerData, boolean useBorderBlockRadius) { + int borderBlockRadius = 0; + if (useBorderBlockRadius && (playerData != null && !playerData.bypassBorderCheck)) { + final int borderRadiusConfig = GriefDefenderPlugin.getActiveConfig(this.world.getUniqueId()).getConfig().claim.borderBlockRadius; + if (borderRadiusConfig > 0 && !this.isUserTrusted((User) playerData.getSubject(), TrustTypes.BUILDER)) { + borderBlockRadius = borderRadiusConfig; + } + } + + boolean inClaim = ( + y >= (this.lesserBoundaryCorner.getY() - borderBlockRadius)) && + y < (this.greaterBoundaryCorner.getY() + 1 + borderBlockRadius) && + x >= (this.lesserBoundaryCorner.getX() - borderBlockRadius) && + x < (this.greaterBoundaryCorner.getX() + 1 + borderBlockRadius) && + z >= (this.lesserBoundaryCorner.getZ() - borderBlockRadius) && + z < (this.greaterBoundaryCorner.getZ() + 1 + borderBlockRadius); + + if (!inClaim) { + return false; + } + + if (!excludeChildren && this.parent != null && (this.getData() == null || (this.getData() != null && this.getData().doesInheritParent()))) { + return this.parent.contains(x, y, z, false, null, false); + } + + return true; + } + + public boolean isClaimOnBorder(GDClaim claim) { + if (claim.cuboid) { + return false; + } + + boolean result = claim.lesserBoundaryCorner.getX() == this.lesserBoundaryCorner.getX() || + claim.greaterBoundaryCorner.getX() == this.greaterBoundaryCorner.getX() || + claim.lesserBoundaryCorner.getZ() == this.lesserBoundaryCorner.getZ() || + claim.greaterBoundaryCorner.getZ() == this.greaterBoundaryCorner.getZ(); + if (claim.cuboid) { + result = claim.lesserBoundaryCorner.getY() == this.lesserBoundaryCorner.getY() || + claim.greaterBoundaryCorner.getY() == this.greaterBoundaryCorner.getY(); + } + return result; + } + + @Override + public boolean overlaps(Claim other) { + GDClaim otherClaim = (GDClaim) other; + if (this.id == otherClaim.id) { + return false; + } + + // Handle claims entirely within a town + if (this.isTown() && !otherClaim.isTown() && otherClaim.isInside(this)) { + return false; + } + + //verify that no claim's lesser boundary point is inside this new claim, to cover the "existing claim is entirely inside new claim" case + if(this.contains(otherClaim.getLesserBoundaryCorner(), false)) { + return true; + } + + return this.isBandingAcross(otherClaim); + } + + //Checks if claim bands across another claim, either horizontally or vertically + public boolean isBandingAcross(GDClaim otherClaim) { + final boolean isClaimInside = otherClaim.isInside(this); + if (isClaimInside) { + return false; + } + + final int smallX = otherClaim.getLesserBoundaryCorner().getX(); + final int smallY = otherClaim.getLesserBoundaryCorner().getY(); + final int smallZ = otherClaim.getLesserBoundaryCorner().getZ(); + final int bigX = otherClaim.getGreaterBoundaryCorner().getX(); + final int bigY = otherClaim.getGreaterBoundaryCorner().getY(); + final int bigZ = otherClaim.getGreaterBoundaryCorner().getZ(); + + if(this.contains(otherClaim.lesserBoundaryCorner, false)) { + return true; + } + if(this.contains(otherClaim.greaterBoundaryCorner, false)) { + return true; + } + if(this.contains(new Vector3i(smallX, 0, bigZ), false)) { + return true; + } + if(this.contains(new Vector3i(bigX, 0, smallZ), false)) { + return true; + } + + boolean inArea = false; + if(this.getLesserBoundaryCorner().getZ() <= bigZ && + this.getLesserBoundaryCorner().getZ() >= smallZ && + this.getLesserBoundaryCorner().getX() < smallX && + this.getGreaterBoundaryCorner().getX() > bigX) + inArea = true; + + if( this.getGreaterBoundaryCorner().getZ() <= bigZ && + this.getGreaterBoundaryCorner().getZ() >= smallZ && + this.getLesserBoundaryCorner().getX() < smallX && + this.getGreaterBoundaryCorner().getX() > bigX ) + inArea = true; + + if( this.getLesserBoundaryCorner().getX() <= bigX && + this.getLesserBoundaryCorner().getX() >= smallX && + this.getLesserBoundaryCorner().getZ() < smallZ && + this.getGreaterBoundaryCorner().getZ() > bigZ ) + inArea = true; + + if( this.getGreaterBoundaryCorner().getX() <= bigX && + this.getGreaterBoundaryCorner().getX() >= smallX && + this.getLesserBoundaryCorner().getZ() < smallZ && + this.getGreaterBoundaryCorner().getZ() > bigZ ) + inArea = true; + + if (inArea) { + // check height + if ((this.lesserBoundaryCorner.getY() >= smallY && + this.lesserBoundaryCorner.getY() <= bigY) || + (this.greaterBoundaryCorner.getY() <= smallY && + this.greaterBoundaryCorner.getY() >= smallY)) { + return true; + } + + return false; + } + + return false; + } + + @Override + public boolean isInside(Claim claim) { + final GDClaim otherClaim = (GDClaim) claim; + if(!otherClaim.contains(this.lesserBoundaryCorner)) { + return false; + } + if(!otherClaim.contains(this.greaterBoundaryCorner)) { + return false; + } + + if(!otherClaim.contains(new Vector3i(this.lesserBoundaryCorner.getX(), this.lesserBoundaryCorner.getY(), this.greaterBoundaryCorner.getZ()))) { + return false; + } + if(!otherClaim.contains(new Vector3i(this.greaterBoundaryCorner.getX(), this.greaterBoundaryCorner.getY(), this.lesserBoundaryCorner.getZ()))) { + return false; + } + + return true; + } + + @Override + public ArrayList<Vector3i> getChunkPositions() { + ArrayList<Vector3i> chunkPositions = new ArrayList<Vector3i>(); + final Set<Long> chunkHashes = this.getChunkHashes(true); + for (Long hash : chunkHashes) { + //chunkPositions.add(ChunkPos.) + } + return chunkPositions; + } + + public ArrayList<Chunk> getChunks() { + ArrayList<Chunk> chunks = new ArrayList<Chunk>(); + + Chunk lesserChunk = this.world + .getChunk(this.getLesserBoundaryCorner().getX() >> 4, 0, this.getLesserBoundaryCorner().getZ() >> 4).orElse(null); + Chunk greaterChunk = this.world + .getChunk(this.getGreaterBoundaryCorner().getX() >> 4, 0, this.getGreaterBoundaryCorner().getZ() >> 4).orElse(null); + + if (lesserChunk != null && greaterChunk != null) { + for (int x = lesserChunk.getPosition().getX(); x <= greaterChunk.getPosition().getX(); x++) { + for (int z = lesserChunk.getPosition().getZ(); z <= greaterChunk.getPosition().getZ(); z++) { + Chunk chunk = world.loadChunk(x, 0, z, true).orElse(null); + if (chunk != null) { + chunks.add(chunk); + } + } + } + } + + return chunks; + } + + public boolean canIgnoreHeight() { + if (this.isCuboid()) { + return false; + } + + if (this.ownerPlayerData != null && (this.getOwnerMinClaimLevel() > 0 || this.getOwnerMaxClaimLevel() < 255)) { + return false; + } + + return true; + } + + public double getOwnerEconomyBlockCost() { + return this.getOwnerEconomyBlockCost(this.ownerPlayerData); + } + + public double getOwnerEconomyBlockCost(GDPlayerData playerData) { + final GDPermissionHolder subject = playerData == null ? GriefDefenderPlugin.DEFAULT_HOLDER : playerData.getSubject(); + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), subject, Options.ECONOMY_BLOCK_COST).doubleValue(); + } + + public int getOwnerMinClaimLevel() { + return this.getOwnerMinClaimLevel(this.ownerPlayerData); + } + + public int getOwnerMinClaimLevel(GDPlayerData playerData) { + final GDPermissionHolder subject = playerData == null ? GriefDefenderPlugin.DEFAULT_HOLDER : playerData.getSubject(); + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), subject, Options.MIN_LEVEL).intValue(); + } + + public int getOwnerMaxClaimLevel() { + return this.getOwnerMaxClaimLevel(this.ownerPlayerData); + } + + public int getOwnerMaxClaimLevel(GDPlayerData playerData) { + final GDPermissionHolder subject = playerData == null ? GriefDefenderPlugin.DEFAULT_HOLDER : playerData.getSubject(); + return GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), subject, Options.MAX_LEVEL).intValue(); + } + + @Override + public Set<Long> getChunkHashes() { + return this.getChunkHashes(true); + } + + public Set<Long> getChunkHashes(boolean refresh) { + if (this.chunkHashes == null || refresh) { + this.chunkHashes = new HashSet<Long>(); + int smallX = this.lesserBoundaryCorner.getX() >> 4; + int smallZ = this.lesserBoundaryCorner.getZ() >> 4; + int largeX = this.greaterBoundaryCorner.getX() >> 4; + int largeZ = this.greaterBoundaryCorner.getZ() >> 4; + + for (int x = smallX; x <= largeX; x++) { + for (int z = smallZ; z <= largeZ; z++) { + this.chunkHashes.add(BlockUtil.getInstance().asLong(x, z)); + } + } + } + + return this.chunkHashes; + } + + @Override + public ClaimData getData() { + return (ClaimData) this.claimData; + } + + public IClaimData getInternalClaimData() { + return this.claimData; + } + + @Nullable + public TownDataConfig getTownData() { + if (!(this.claimData instanceof TownDataConfig)) { + return null; + } + + return (TownDataConfig) this.claimData; + } + + public ClaimStorageData getClaimStorage() { + return this.claimStorage; + } + + public void setClaimData(IClaimData data) { + this.claimData = data; + } + + public void setClaimStorage(ClaimStorageData storage) { + this.claimStorage = storage; + } + + public void updateClaimStorageData() { + if (!this.isAdminClaim()) { + this.claimStorage.getConfig().setOwnerUniqueId(this.getOwnerUniqueId()); + } + this.claimStorage.getConfig().setWorldUniqueId(this.world.getUniqueId()); + this.claimData.setCuboid(this.cuboid); + this.claimData.setType(this.type); + this.claimData.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(this.lesserBoundaryCorner)); + this.claimData.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(this.greaterBoundaryCorner)); + // Will save next world save + this.claimData.setRequiresSave(true); + } + + public void save() { + for (Claim child : this.children) { + GDClaim childClaim = (GDClaim) child; + if (childClaim.getInternalClaimData().requiresSave()) { + childClaim.save(); + } + } + GDSaveClaimEvent.Pre preEvent = new GDSaveClaimEvent.Pre(this); + GriefDefender.getEventManager().post(preEvent); + if (this.getInternalClaimData().requiresSave()) { + this.updateClaimStorageData(); + this.getClaimStorage().save(); + this.getInternalClaimData().setRequiresSave(false); + } + GDSaveClaimEvent.Post postEvent = new GDSaveClaimEvent.Post(this); + GriefDefender.getEventManager().post(postEvent); + } + + public boolean isPvpEnabled() { + Tristate value = this.claimData.getPvpOverride(); + if (value != Tristate.UNDEFINED) { + return value.asBoolean(); + } + + return this.world.getProperties().isPVPEnabled(); + } + + public void setPvpOverride(Tristate value) { + this.claimData.setPvpOverride(value); + this.getClaimStorage().save(); + } + + @Override + public ClaimResult transferOwner(UUID newOwnerID) { + return this.transferOwner(newOwnerID, false, false); + } + + public ClaimResult transferOwner(UUID newOwnerID, boolean checkEconomy, boolean withdrawFunds) { + if (this.isWilderness()) { + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, TextComponent.builder("").append("The wilderness cannot be transferred.", TextColor.RED).build()); + } + + if (this.isAdminClaim()) { + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, TextComponent.builder("").append("Admin claims cannot be transferred.", TextColor.RED).build()); + } + + GDPlayerData ownerData = DATASTORE.getOrCreatePlayerData(this.world.getUniqueId(), this.getOwnerUniqueId()); + // determine new owner + GDPlayerData newOwnerData = DATASTORE.getOrCreatePlayerData(this.world.getUniqueId(), newOwnerID); + + if (this.isBasicClaim() && this.claimData.requiresClaimBlocks()) { + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + if (checkEconomy) { + final GDClaimResult result = EconomyUtil.getInstance().checkEconomyFunds(this, newOwnerData, withdrawFunds); + if (!result.successful()) { + return result; + } + } + } else { + int remainingClaimBlocks = newOwnerData.getRemainingClaimBlocks(); + if (remainingClaimBlocks < 0 || (this.getClaimBlocks() > remainingClaimBlocks)) { + return new GDClaimResult(ClaimResultType.INSUFFICIENT_CLAIM_BLOCKS); + } + } + } + + // Check limits + final Player currentOwner = ownerData.getSubject() instanceof Player ? (Player) ownerData.getSubject() : null; + final int createClaimLimit = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), newOwnerData.getSubject(), Options.CREATE_LIMIT, this); + if (createClaimLimit > -1 && (newOwnerData.getInternalClaims().size() + 1) > createClaimLimit) { + if (currentOwner != null) { + GriefDefenderPlugin.sendMessage(currentOwner, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_TRANSFER_EXCEEDS_LIMIT)); + } + return new GDClaimResult(this, ClaimResultType.EXCEEDS_MAX_CLAIM_LIMIT, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_TRANSFER_EXCEEDS_LIMIT)); + } + + // transfer + GDTransferClaimEvent event = new GDTransferClaimEvent(this, this.getOwnerUniqueId(), newOwnerID); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(this, ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + if (this.isAdminClaim()) { + // convert to basic + this.type = ClaimTypes.BASIC; + this.getVisualizer().setType(ClaimVisual.BASIC); + this.claimData.setType(ClaimTypes.BASIC); + } + + this.ownerUniqueId = event.getNewOwner(); + if (!this.getOwnerUniqueId().equals(newOwnerID)) { + newOwnerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.world, this.getOwnerUniqueId()); + } + + this.claimData.setOwnerUniqueId(newOwnerID); + if (this.isBasicClaim()) { + ownerData.getInternalClaims().remove(this); + newOwnerData.getInternalClaims().add(this); + } + + this.ownerPlayerData = newOwnerData; + this.getClaimStorage().save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + public ClaimResult findParent(GDClaim claimToSearch) { + if (!this.isInside(claimToSearch)) { + return new GDClaimResult(ClaimResultType.CLAIM_NOT_FOUND); + } + Claim current = claimToSearch; + for (Claim child : current.getChildren(true)) { + if (this.isInside(child)) { + current = child; + } + } + return new GDClaimResult(current, ClaimResultType.SUCCESS); + } + + public ClaimResult doesClaimOverlap() { + if (this.parent != null) { + final GDClaim parentClaim = (GDClaim) this.parent; + // 1 - Make sure new claim is inside parent + if (!this.isInside(parentClaim)) { + return new GDClaimResult(parentClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + + // 2 - Check parent children + for (Claim child : parentClaim.children) { + final GDClaim childClaim = (GDClaim) child; + if (this.isBandingAcross(childClaim) || childClaim.isBandingAcross(this)) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + final Set<Long> chunkHashes = this.getChunkHashes(true); + + // Since there is no parent we need to check all claims stored in chunk hashes + for (Long chunkHash : chunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk == null || claimsInChunk.size() == 0) { + continue; + } + for (Claim child : claimsInChunk) { + final GDClaim gpChild = (GDClaim) child; + // First check if newly resized claim is crossing another + if (this.isBandingAcross(gpChild) || gpChild.isBandingAcross(this)) { + return new GDClaimResult(child, ClaimResultType.OVERLAPPING_CLAIM); + } + } + } + + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + // Scans area for any overlaps and migrates children to a newly created or resized claim + public ClaimResult checkArea(boolean resize) { + final List<Claim> claimsInArea = new ArrayList<>(); + claimsInArea.add(this); + + if (this.parent != null) { + return checkAreaParent(claimsInArea, resize); + } + + final List<Claim> claimsToMigrate = new ArrayList<>(); + // First check children + for (Claim child : this.children) { + final GDClaim childClaim = (GDClaim) child; + if (this.isBandingAcross(childClaim) || childClaim.isBandingAcross(this)) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + if (childClaim.isInside(this)) { + if (!this.isAdminClaim()) { + if (this.type.equals(childClaim.type) || childClaim.isAdminClaim()) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + } else { + // child is no longer within parent + // if resizing, migrate the child claim out + if (resize) { + claimsToMigrate.add(childClaim); + } else { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + } + + if (!claimsToMigrate.isEmpty()) { + ((GDClaim) this.wildernessClaim).migrateClaims(claimsToMigrate); + } + + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + final Set<Long> chunkHashes = this.getChunkHashes(true); + + // Since there is no parent we need to check all claims stored in chunk hashes + for (Long chunkHash : chunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk == null || claimsInChunk.size() == 0) { + continue; + } + for (Claim chunkClaim : claimsInChunk) { + final GDClaim gpChunkClaim = (GDClaim) chunkClaim; + if (gpChunkClaim.equals(this) || claimsInArea.contains(gpChunkClaim)) { + continue; + } + if (this.isAdminClaim() && gpChunkClaim.isAdminClaim() && gpChunkClaim.parent != null && gpChunkClaim.parent.equals(this)) { + continue; + } + + // First check if new claim is crossing another + if (this.isBandingAcross(gpChunkClaim) || gpChunkClaim.isBandingAcross(this)) { + return new GDClaimResult(gpChunkClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + if (gpChunkClaim.isInside(this)) { + if (!this.isAdminClaim()) { + if (this.type.equals(gpChunkClaim.type) || gpChunkClaim.isAdminClaim()) { + return new GDClaimResult(gpChunkClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + if (!this.canEnclose(gpChunkClaim)) { + return new GDClaimResult(gpChunkClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + if (!this.isSubdivision()) { + claimsInArea.add(gpChunkClaim); + } + } else if (this.isInside(gpChunkClaim)) { + // Fix WorldEdit issue + // Make sure to check if chunk claim can enclose newly created claim + if (!gpChunkClaim.canEnclose(this)) { + return new GDClaimResult(gpChunkClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + } + } + + return new GDClaimResult(claimsInArea, ClaimResultType.SUCCESS); + } + + public ClaimResult checkAreaParent(List<Claim> claimsInArea, boolean resize) { + if (this.isClaimOnBorder(this.parent)) { + return new GDClaimResult(this.parent, ClaimResultType.OVERLAPPING_CLAIM); + } + final GDClaim parentClaim = (GDClaim) this.parent; + // 1 - Make sure new claim is inside parent + if (!this.isInside(parentClaim)) { + return new GDClaimResult(parentClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + + // 2 - Check parent children + for (Claim child : parentClaim.children) { + final GDClaim childClaim = (GDClaim) child; + if (this.equals(child)) { + continue; + } + if (this.isBandingAcross(childClaim) || childClaim.isBandingAcross(this)) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + + if (childClaim.isInside(this)) { + if (!this.isAdminClaim()) { + if (this.type.equals(childClaim.type) || childClaim.isAdminClaim()) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + } + if (!this.isSubdivision()) { + claimsInArea.add(childClaim); + } + } + // ignore claims not inside + } + + if (resize) { + // Make sure children are still within their parent + final List<Claim> claimsToMigrate = new ArrayList<>(); + for (Claim child : this.children) { + GDClaim childClaim = (GDClaim) child; + if (this.isBandingAcross(childClaim) || childClaim.isBandingAcross(this)) { + return new GDClaimResult(childClaim, ClaimResultType.OVERLAPPING_CLAIM); + } + if (!childClaim.isInside(this)) { + if (this.parent != null) { + claimsToMigrate.add(childClaim); + } else { + childClaim.parent = null; + this.children.remove(childClaim); + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + claimWorldManager.addClaim(childClaim, true); + } + } + } + if (!claimsToMigrate.isEmpty()) { + this.parent.migrateClaims(claimsToMigrate); + } + } + return new GDClaimResult(claimsInArea, ClaimResultType.SUCCESS); + } + + public boolean canEnclose(Claim claim) { + if (claim.isWilderness()) { + return false; + } + if (this.isAdminClaim()) { + // admin claims can enclose any type + return true; + } + if (this.isSubdivision()) { + return false; + } + if (this.isBasicClaim()) { + if (!claim.isSubdivision()) { + return false; + } + return true; + } + if (this.isTown()) { + if (claim.isAdminClaim()) { + return false; + } + return true; + } + return true; + } + + // Checks to see if the passed in claim is a parent of this claim + @Override + public boolean isParent(Claim claim) { + if (this.parent == null) { + return false; + } + + GDClaim parent = this.parent; + while (parent != null) { + if (parent.getUniqueId().equals(claim.getUniqueId())) { + return true; + } + parent = parent.parent; + } + + return false; + } + + @Override + public ClaimResult resize(int x1, int x2, int y1, int y2, int z1, int z2) { + int minx = Math.min(x1, x2); + int miny = Math.min(y1, y2); + int minz = Math.min(z1, z2); + int maxx = Math.max(x1, x2); + int maxy = Math.max(y1, y2); + int maxz = Math.max(z1, z2); + + final Object root = GDCauseStackManager.getInstance().getCurrentCause().root(); + final GDPermissionUser user = root instanceof GDPermissionUser ? (GDPermissionUser) root : null; + final Player player = user != null ? user.getOnlinePlayer() : null; + if (this.cuboid) { + return resizeCuboid(player, minx, miny, minz, maxx, maxy, maxz); + } + + Location<World> startCorner = null; + Location<World> endCorner = null; + GDPlayerData playerData = null; + if (player != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(this.world, player.getUniqueId()); + } else if (!this.isAdminClaim() && this.ownerUniqueId != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(this.world, this.ownerUniqueId); + } + + if (playerData == null) { + if (GriefDefenderPlugin.getActiveConfig(this.world.getUniqueId()).getConfig().claim.claimAutoSchematicRestore) { + return new GDClaimResult(this, ClaimResultType.FAILURE); + } + startCorner = new Location<World>(this.world, minx, miny, minz); + endCorner = new Location<World>(this.world, maxx, maxy, maxz); + } else { + if (!playerData.canIgnoreClaim(this) && GriefDefenderPlugin.getActiveConfig(this.world.getUniqueId()).getConfig().claim.claimAutoSchematicRestore) { + return new GDClaimResult(this, ClaimResultType.FAILURE); + } + startCorner = playerData.lastShovelLocation; + endCorner = playerData.endShovelLocation; + } + + // Auto-adjust Y levels for 2D claims + if (playerData != null) { + miny = this.getOwnerMinClaimLevel(); + } + if (playerData != null) { + maxy = this.getOwnerMaxClaimLevel(); + } + Vector3i currentLesserCorner = this.getLesserBoundaryCorner(); + Vector3i currentGreaterCorner = this.getGreaterBoundaryCorner(); + Vector3i newLesserCorner = new Vector3i(minx, miny, minz); + Vector3i newGreaterCorner = new Vector3i(maxx, maxy, maxz); + + GDChangeClaimEvent.Resize event = new GDChangeClaimEvent.Resize(this, startCorner, endCorner); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(this, ClaimResultType.CLAIM_EVENT_CANCELLED); + } + + // check player has enough claim blocks + if ((this.isBasicClaim() || this.isTown()) && this.claimData.requiresClaimBlocks()) { + final int newCost = BlockUtil.getInstance().getClaimBlockCost(this.world, newLesserCorner, newGreaterCorner, this.cuboid); + final int currentCost = BlockUtil.getInstance().getClaimBlockCost(this.world, currentLesserCorner, currentGreaterCorner, this.cuboid); + if (GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(playerData.playerID).orElse(null); + if (playerAccount == null) { + return new GDClaimResult(ClaimResultType.ECONOMY_ACCOUNT_NOT_FOUND); + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final double requiredFunds = Math.abs((newCost - currentCost) * this.getOwnerEconomyBlockCost()); + if (newCost > currentCost) { + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + final TransactionResult result = playerAccount.withdraw(defaultCurrency, BigDecimal.valueOf(requiredFunds), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() != ResultType.SUCCESS) { + Component message = null; + if (player != null) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.ECONOMY_NOT_ENOUGH_FUNDS, ImmutableMap.of( + "balance", currentFunds.doubleValue(), + "amount", requiredFunds)); + GriefDefenderPlugin.sendMessage(player, message); + } + + playerData.lastShovelLocation = null; + playerData.claimResizing = null; + return new GDClaimResult(ClaimResultType.ECONOMY_NOT_ENOUGH_FUNDS, message); + } + } else { + final TransactionResult result = playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(requiredFunds), Sponge.getCauseStackManager().getCurrentCause()); + } + } else if (newCost > currentCost) { + final int remainingClaimBlocks = this.ownerPlayerData.getRemainingClaimBlocks() - (newCost - currentCost); + if (remainingClaimBlocks < 0) { + if (player != null) { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = Math.abs(remainingClaimBlocks / 65536.0); + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_NEED_BLOCKS_3D, ImmutableMap.of( + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", Math.abs(remainingClaimBlocks)))); + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_SIZE_NEED_BLOCKS_2D, ImmutableMap.of( + "block-amount", Math.abs(remainingClaimBlocks)))); + } + } + playerData.lastShovelLocation = null; + playerData.claimResizing = null; + this.lesserBoundaryCorner = currentLesserCorner; + this.greaterBoundaryCorner = currentGreaterCorner; + return new GDClaimResult(ClaimResultType.INSUFFICIENT_CLAIM_BLOCKS); + } + } + } + + this.lesserBoundaryCorner = newLesserCorner; + this.greaterBoundaryCorner = newGreaterCorner; + + // checkArea refreshes the current chunk hashes so it is important + // to make a copy before making the call + final Set<Long> currentChunkHashes = new HashSet<>(this.chunkHashes); + + final ClaimResult result = this.checkArea(true); + if (!result.successful()) { + this.lesserBoundaryCorner = currentLesserCorner; + this.greaterBoundaryCorner = currentGreaterCorner; + return result; + } + + if (this.type != ClaimTypes.ADMIN && this.type != ClaimTypes.WILDERNESS) { + ClaimResult claimResult = checkSizeLimits(player, playerData, newLesserCorner, newGreaterCorner); + if (!claimResult.successful()) { + this.lesserBoundaryCorner = currentLesserCorner; + this.greaterBoundaryCorner = currentGreaterCorner; + return claimResult; + } + } + + // This needs to be adjusted before we check for overlaps + this.lesserBoundaryCorner = newLesserCorner; + this.greaterBoundaryCorner = newGreaterCorner; + GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + + // resize validated, remove invalid chunkHashes + if (this.parent == null) { + for (Long chunkHash : currentChunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk != null && claimsInChunk.size() > 0) { + claimsInChunk.remove(this); + } + } + + final Set<Long> newChunkHashes = this.getChunkHashes(true); + // add new chunk hashes + for (Long chunkHash : newChunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk == null) { + claimsInChunk = new HashSet<>(); + claimWorldManager.getInternalChunksToClaimsMap().put(chunkHash, claimsInChunk); + } + + claimsInChunk.add(this); + } + } + + this.claimData.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(this.lesserBoundaryCorner)); + this.claimData.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(this.greaterBoundaryCorner)); + this.claimData.setRequiresSave(true); + this.getClaimStorage().save(); + + if (result.getClaims().size() > 1) { + this.migrateClaims(new ArrayList<>(result.getClaims())); + } + this.resetVisuals(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + public ClaimResult resizeCuboid(Player player, int smallX, int smallY, int smallZ, int bigX, int bigY, int bigZ) { + // make sure resize doesn't cross paths + if (smallX >= bigX || smallY >= bigY || smallZ >= bigZ) { + return new GDClaimResult(this, ClaimResultType.OVERLAPPING_CLAIM); + } + + Location<World> startCorner = null; + Location<World> endCorner = null; + GDPlayerData playerData = null; + + if (player != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(this.world, player.getUniqueId()); + } else if (!this.isAdminClaim() && this.ownerUniqueId != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(this.world, this.ownerUniqueId); + } + + if (playerData == null) { + if (GriefDefenderPlugin.getActiveConfig(this.world.getUniqueId()).getConfig().claim.claimAutoSchematicRestore) { + return new GDClaimResult(this, ClaimResultType.FAILURE); + } + startCorner = new Location<World>(this.world, smallX, smallY, smallZ); + endCorner = new Location<World>(this.world, bigX, bigY, bigZ); + } else { + if (!playerData.canIgnoreClaim(this) && GriefDefenderPlugin.getActiveConfig(this.world.getUniqueId()).getConfig().claim.claimAutoSchematicRestore) { + return new GDClaimResult(this, ClaimResultType.FAILURE); + } + startCorner = playerData.lastShovelLocation; + endCorner = playerData.endShovelLocation; + } + + GDChangeClaimEvent.Resize event = new GDChangeClaimEvent.Resize(this, startCorner, endCorner); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(this, ClaimResultType.CLAIM_EVENT_CANCELLED); + } + + final int minClaimLevel = this.getOwnerMinClaimLevel(); + if (playerData != null && playerData.shovelMode != ShovelTypes.ADMIN && smallY < minClaimLevel) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_BELOW_LEVEL, ImmutableMap.of( + "limit", minClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return new GDClaimResult(ClaimResultType.BELOW_MIN_LEVEL); + } + final int maxClaimLevel = this.getOwnerMaxClaimLevel(); + if (playerData != null && playerData.shovelMode != ShovelTypes.ADMIN && bigY > maxClaimLevel) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_ABOVE_LEVEL, ImmutableMap.of( + "limit", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return new GDClaimResult(ClaimResultType.ABOVE_MAX_LEVEL); + } + // check if child extends past parent limits + if (this.parent != null) { + if (smallX < this.parent.getLesserBoundaryCorner().getX() || + smallY < this.parent.getLesserBoundaryCorner().getY() || + smallZ < this.parent.getLesserBoundaryCorner().getZ()) { + return new GDClaimResult(this.parent, ClaimResultType.OVERLAPPING_CLAIM); + } + if (bigX > this.parent.getGreaterBoundaryCorner().getX() || + (this.parent.isCuboid() && bigY > this.parent.getGreaterBoundaryCorner().getY()) || + bigZ > this.parent.getGreaterBoundaryCorner().getZ()) { + return new GDClaimResult(this.parent, ClaimResultType.OVERLAPPING_CLAIM); + } + } + + Vector3i currentLesserCorner = this.lesserBoundaryCorner; + Vector3i currentGreaterCorner = this.greaterBoundaryCorner; + Vector3i newLesserCorner = new Vector3i(smallX, smallY, smallZ); + Vector3i newGreaterCorner = new Vector3i(bigX, bigY, bigZ); + this.lesserBoundaryCorner = newLesserCorner; + this.greaterBoundaryCorner = newGreaterCorner; + + // checkArea refreshes the current chunk hashes so it is important + // to make a copy before making the call + final Set<Long> currentChunkHashes = new HashSet<>(this.chunkHashes); + + final ClaimResult result = this.checkArea(true); + if (!result.successful()) { + this.lesserBoundaryCorner = currentLesserCorner; + this.greaterBoundaryCorner = currentGreaterCorner; + return result; + } + + if (this.type != ClaimTypes.ADMIN && this.type != ClaimTypes.WILDERNESS) { + ClaimResult claimResult = checkSizeLimits(player, playerData, newLesserCorner, newGreaterCorner); + if (!claimResult.successful()) { + this.lesserBoundaryCorner = currentLesserCorner; + this.greaterBoundaryCorner = currentGreaterCorner; + return claimResult; + } + } + + this.lesserBoundaryCorner = newLesserCorner; + this.greaterBoundaryCorner = newGreaterCorner; + // resize validated, remove invalid chunkHashes + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + if (this.parent == null) { + for (Long chunkHash : currentChunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk != null && claimsInChunk.size() > 0) { + claimsInChunk.remove(this); + } + } + + final Set<Long> newChunkHashes = this.getChunkHashes(true); + // add new chunk hashes + for (Long chunkHash : newChunkHashes) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk == null) { + claimsInChunk = new HashSet<>(); + claimWorldManager.getInternalChunksToClaimsMap().put(chunkHash, claimsInChunk); + } + + claimsInChunk.add(this); + } + } + + this.claimData.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(this.lesserBoundaryCorner)); + this.claimData.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(this.greaterBoundaryCorner)); + this.claimData.setRequiresSave(true); + this.getClaimStorage().save(); + if (result.getClaims().size() > 1) { + this.migrateClaims(new ArrayList<>(result.getClaims())); + } + this.resetVisuals(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + private ClaimResult checkSizeLimits(Player player, GDPlayerData playerData, Vector3i lesserCorner, Vector3i greaterCorner) { + if (playerData == null) { + return new GDClaimResult(ClaimResultType.SUCCESS); + } + + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateUser(playerData.playerID); + final int minClaimX = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MIN_SIZE_X, this); + final int minClaimY = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MIN_SIZE_Y, this); + final int minClaimZ = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MIN_SIZE_Z, this); + final int maxClaimX = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MAX_SIZE_X, this); + final int maxClaimY = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MAX_SIZE_Y, this); + final int maxClaimZ = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), holder, Options.MAX_SIZE_Z, this); + + // Handle single block selection + if ((this.isCuboid() && greaterCorner.equals(lesserCorner)) || (!this.isCuboid() && greaterCorner.getX() == lesserCorner.getX() && greaterCorner.getZ() == lesserCorner.getZ())) { + if (playerData.claimResizing != null) { + final Component message = MessageCache.getInstance().RESIZE_SAME_LOCATION; + GriefDefenderPlugin.sendMessage(player, message); + playerData.lastShovelLocation = null; + playerData.claimResizing = null; + // TODO: Add new result type for this + return new GDClaimResult(ClaimResultType.BELOW_MIN_SIZE_X, message); + } + if (playerData.claimSubdividing == null) { + final Component message = MessageCache.getInstance().CREATE_SUBDIVISION_ONLY; + GriefDefenderPlugin.sendMessage(player, message); + playerData.lastShovelLocation = null; + // TODO: Add new result type for this + return new GDClaimResult(ClaimResultType.BELOW_MIN_SIZE_X, message); + } + } + Component message = null; + String maxCuboidArea = maxClaimX + "x" + maxClaimY + "x" + maxClaimZ; + if (maxClaimX == 0 && maxClaimY == 0 && maxClaimZ == 0) { + maxCuboidArea = "∞"; + } + String maxArea = maxClaimX + "x" + maxClaimZ; + if (maxClaimX == 0 && maxClaimZ == 0) { + maxArea = "∞"; + } + + if (maxClaimX > 0) { + int size = Math.abs(greaterCorner.getX() - lesserCorner.getX()) + 1; + if (size > maxClaimX) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "x", + "size", size, + "max-size", maxClaimX == 0 ? "∞" : maxClaimX, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "x", + "size", size, + "max-size", maxClaimX == 0 ? "∞" : maxClaimX, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.EXCEEDS_MAX_SIZE_X, message); + } + } + if (this.cuboid && maxClaimY > 0) { + int size = Math.abs(greaterCorner.getY() - lesserCorner.getY()) + 1; + if (size > maxClaimY) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "y", + "size", size, + "max-size", maxClaimY == 0 ? "∞" : maxClaimY, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "y", + "size", size, + "max-size", maxClaimY == 0 ? "∞" : maxClaimY, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.EXCEEDS_MAX_SIZE_Y, message); + } + } + if (maxClaimZ > 0) { + int size = Math.abs(greaterCorner.getZ() - lesserCorner.getZ()) + 1; + if (size > maxClaimZ) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "z", + "size", size, + "max-size", maxClaimZ == 0 ? "∞" : maxClaimZ, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MAX, ImmutableMap.of( + "axis", "z", + "size", size, + "max-size", maxClaimZ == 0 ? "∞" : maxClaimZ, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.EXCEEDS_MAX_SIZE_Z, message); + } + } + if (minClaimX > 0) { + int size = Math.abs(greaterCorner.getX() - lesserCorner.getX()) + 1; + if (size < minClaimX) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "x", + "size", size, + "min-size", minClaimX == 0 ? "∞" : minClaimX, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "x", + "size", size, + "min-size", minClaimX, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.BELOW_MIN_SIZE_X, message); + } + } + if (this.cuboid && minClaimY > 0) { + int size = Math.abs(greaterCorner.getY() - lesserCorner.getY()) + 1; + if (size < minClaimY) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "y", + "size", size, + "min-size", minClaimY == 0 ? "∞" : minClaimY, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "y", + "size", size, + "min-size", minClaimY == 0 ? "∞" : minClaimY, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.BELOW_MIN_SIZE_Y, message); + } + } + if (minClaimZ > 0) { + int size = Math.abs(greaterCorner.getZ() - lesserCorner.getZ()) + 1; + if (size < minClaimZ) { + if (player != null) { + if (this.isCuboid()) { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "z", + "size", size, + "min-size", minClaimZ == 0 ? "∞" : minClaimZ, + "min-area", minClaimX + "x" + minClaimY + "x" + minClaimZ, + "max-area", maxCuboidArea)); + } else { + message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIM_SIZE_MIN, ImmutableMap.of( + "axis", "z", + "size", size, + "min-size", minClaimZ == 0 ? "∞" : minClaimZ, + "min-area", minClaimX + "x" + minClaimZ, + "max-area", maxArea)); + } + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(ClaimResultType.BELOW_MIN_SIZE_Z, message); + } + } + + return new GDClaimResult(ClaimResultType.SUCCESS); + } + + public void unload() { + // clear any references + this.world = null; + if (this.ownerPlayerData != null) { + this.ownerPlayerData.getInternalClaims().remove(this); + } + } + + @Override + public Claim getWilderness() { + return this.wildernessClaim; + } + + @Override + public ClaimManager getClaimManager() { + return (ClaimManager) this.worldClaimManager; + } + + @Override + public Context getContext() { + return this.context; + } + + public org.spongepowered.api.service.context.Context getSpongeContext() { + return this.spongeContext; + } + + public Context getInheritContext() { + if (this.parent == null || !this.getData().doesInheritParent()) { + return this.context; + } + + return this.parent.getInheritContext(); + } + + public boolean hasAdminParent() { + if (this.parent == null || this.isAdminClaim()) { + return false; + } + + if (this.parent.isAdminClaim()) { + return true; + } + + return this.parent.hasAdminParent(); + } + + @Override + public boolean extend(int newDepth) { + return false; + } + + @Override + public Optional<Claim> getParent() { + return Optional.ofNullable(this.parent); + } + + @Override + public UUID getWorldUniqueId() { + return this.world.getUniqueId(); + } + + public World getWorld() { + return this.world; + } + + public void setWorld(World world) { + this.world = world; + } + /*@Override + public List<Entity> getEntities() { + Collection<Entity> worldEntityList = Sponge.getServer().getWorld(this.world.getUniqueId()).get().getEntities(); + List<Entity> entityList = new ArrayList<>(); + for (Entity entity : worldEntityList) { + if (!(entity.isRemoved() && this.contains(VecHelper.toVector3i(entity.getLocation())))) { + entityList.add(entity); + } + } + + return entityList; + } + + @Override + public List<Player> getPlayers() { + Collection<Player> worldPlayerList = Sponge.getServer().getWorld(this.world.getUniqueId()).get().getPlayers(); + List<Player> playerList = new ArrayList<>(); + for (Player player : worldPlayerList) { + if (!(player.isRemoved() && this.contains(VecHelper.toVector3i(player.getLocation())))) { + playerList.add(player); + } + } + + return playerList; + }*/ + @Override + public List<UUID> getPlayers() { + Collection<Player> worldPlayerList = Sponge.getServer().getWorld(this.world.getUniqueId()).get().getPlayers(); + List<UUID> playerList = new ArrayList<>(); + for (Player player : worldPlayerList) { + if (!(player.isRemoved() && this.contains(player.getLocation().getBlockPosition()))) { + playerList.add(player.getUniqueId()); + } + } + + return playerList; + } + + @Override + public Set<Claim> getChildren(boolean recursive) { + if (recursive) { + Set<Claim> claimList = new HashSet<>(this.children); + List<Claim> subChildren = new ArrayList<>(); + for (Claim child : claimList) { + GDClaim childClaim = (GDClaim) child; + if (!childClaim.children.isEmpty()) { + subChildren.addAll(childClaim.getChildren(true)); + } + } + claimList.addAll(subChildren); + return claimList; + } + return ImmutableSet.copyOf(this.children); + } + + @Override + public List<Claim> getParents(boolean recursive) { + List<Claim> parents = new ArrayList<>(); + GDClaim currentClaim = this; + while (currentClaim.parent != null) { + parents.add(currentClaim.parent); + currentClaim = currentClaim.parent; + } + + // Index 0 is highest parent while last index represents direct + Collections.reverse(parents); + return ImmutableList.copyOf(parents); + } + + public List<Claim> getInheritedParents() { + List<Claim> parents = new ArrayList<>(); + GDClaim currentClaim = this; + while (currentClaim.parent != null && (currentClaim.getData() == null || currentClaim.getData().doesInheritParent())) { + if (currentClaim.isAdminClaim()) { + if (currentClaim.parent.isAdminClaim()) { + parents.add(currentClaim.parent); + } + } else { + parents.add(currentClaim.parent); + } + currentClaim = currentClaim.parent; + } + + // Index 0 is highest parent while last index represents direct + Collections.reverse(parents); + return ImmutableList.copyOf(parents); + } + + @Override + public ClaimResult deleteChild(Claim child) { + boolean found = false; + for (Claim childClaim : this.children) { + if (childClaim.getUniqueId().equals(child.getUniqueId())) { + found = true; + } + } + + if (!found) { + return new GDClaimResult(ClaimResultType.CLAIM_NOT_FOUND); + } + + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + return claimManager.deleteClaim(child, true); + } + + @Override + public ClaimResult deleteChildren() { + return this.deleteChildren(null); + } + + @Override + public ClaimResult deleteChildren(ClaimType claimType) { + List<Claim> claimList = new ArrayList<>(); + for (Claim child : this.children) { + if (claimType == null || child.getType() == claimType) { + claimList.add(child); + } + } + + if (claimList.isEmpty()) { + return new GDClaimResult(ClaimResultType.CLAIM_NOT_FOUND); + } + + GDRemoveClaimEvent event = new GDRemoveClaimEvent(claimList); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(claimList, ClaimResultType.CLAIM_EVENT_CANCELLED); + } + + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + for (Claim child : claimList) { + claimManager.deleteClaimInternal(child, true); + } + + return new GDClaimResult(event.getClaims(), ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult changeType(ClaimType type, Optional<UUID> ownerUniqueId) { + return changeType(type, ownerUniqueId, null); + } + + public ClaimResult changeType(ClaimType type, Optional<UUID> ownerUniqueId, CommandSource src) { + if (type == this.type) { + return new GDClaimResult(ClaimResultType.SUCCESS); + } + + final Player sourcePlayer = src instanceof Player ? (Player) src : null; + GDChangeClaimEvent.Type event = new GDChangeClaimEvent.Type(this, type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.world.getUniqueId()); + final GDPlayerData sourcePlayerData = src != null && src instanceof Player ? claimWorldManager.getOrCreatePlayerData(((Player) src).getUniqueId()) : null; + UUID newOwnerUUID = ownerUniqueId.orElse(this.ownerUniqueId); + final ClaimResult result = this.validateClaimType(type, newOwnerUUID, sourcePlayerData); + if (!result.successful()) { + return result; + } + + if (type == ClaimTypes.ADMIN) { + newOwnerUUID = GriefDefenderPlugin.ADMIN_USER_UUID; + } + + final String fileName = this.getClaimStorage().filePath.getFileName().toString(); + final Path newPath = this.getClaimStorage().folderPath.getParent().resolve(type.getName().toLowerCase()).resolve(fileName); + try { + if (Files.notExists(newPath.getParent())) { + Files.createDirectories(newPath.getParent()); + } + Files.move(this.getClaimStorage().filePath, newPath); + if (type == ClaimTypes.TOWN) { + this.setClaimStorage(new TownStorageData(newPath, this.getWorldUniqueId(), newOwnerUUID, this.cuboid)); + } else { + this.setClaimStorage(new ClaimStorageData(newPath, this.getWorldUniqueId(), (ClaimDataConfig) this.getInternalClaimData())); + } + this.claimData = this.claimStorage.getConfig(); + this.getClaimStorage().save(); + } catch (IOException e) { + e.printStackTrace(); + return new GDClaimResult(ClaimResultType.CLAIM_NOT_FOUND, TextComponent.of(e.getMessage())); + } + + // If switched to admin or new owner, remove from player claim list + if (type == ClaimTypes.ADMIN || !this.ownerUniqueId.equals(newOwnerUUID)) { + final Set<Claim> currentPlayerClaims = claimWorldManager.getInternalPlayerClaims(this.ownerUniqueId); + if (currentPlayerClaims != null) { + currentPlayerClaims.remove(this); + } + } + if (type != ClaimTypes.ADMIN) { + final Set<Claim> newPlayerClaims = claimWorldManager.getInternalPlayerClaims(newOwnerUUID); + if (newPlayerClaims != null && !newPlayerClaims.contains(this)) { + newPlayerClaims.add(this); + } + } + + if (!this.isAdminClaim() && this.ownerPlayerData != null) { + final Player player = Sponge.getServer().getPlayer(this.ownerUniqueId).orElse(null); + if (player != null) { + this.ownerPlayerData.revertActiveVisual(player); + } + } + + // revert visuals for all players watching this claim + List<UUID> playersWatching = new ArrayList<>(this.playersWatching); + for (UUID playerUniqueId : playersWatching) { + final Player spongePlayer = Sponge.getServer().getPlayer(playerUniqueId).orElse(null); + final GDPlayerData playerData = claimWorldManager.getOrCreatePlayerData(playerUniqueId); + if (spongePlayer != null) { + playerData.revertActiveVisual(spongePlayer); + } + } + + if (!newOwnerUUID.equals(GriefDefenderPlugin.ADMIN_USER_UUID)) { + this.setOwnerUniqueId(newOwnerUUID); + } + this.setType(type); + this.claimVisual = null; + this.getInternalClaimData().setRequiresSave(true); + this.getClaimStorage().save(); + return new GDClaimResult(ClaimResultType.SUCCESS); + } + + public ClaimResult validateClaimType(ClaimType type, UUID newOwnerUUID, GDPlayerData playerData) { + boolean isAdmin = false; + if (playerData != null && (playerData.canManageAdminClaims || playerData.canIgnoreClaim(this))) { + isAdmin = true; + } + + if (type == ClaimTypes.ADMIN) { + if (!isAdmin) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_CHANGE_NOT_ADMIN, + ImmutableMap.of("type", TextComponent.of("ADMIN").color(TextColor.RED))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + } else if (type == ClaimTypes.BASIC) { + if (this.isAdminClaim() && newOwnerUUID == null) { + return new GDClaimResult(ClaimResultType.REQUIRES_OWNER, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_REQUIRES_OWNER, + ImmutableMap.of( + "type", TextComponent.of("ADMIN", TextColor.RED), + "target_type", TextComponent.of("BASIC", TextColor.GREEN)))); + } + if (this.parent != null && this.parent.isBasicClaim()) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_CHILD_SAME, + ImmutableMap.of("type", TextComponent.of("BASIC").color(TextColor.GREEN))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + for (Claim child : this.children) { + if (!child.isSubdivision()) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_ONLY_SUBDIVISION, + ImmutableMap.of("type", TextComponent.of("BASIC").color(TextColor.GREEN))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + } + } else if (type == ClaimTypes.SUBDIVISION) { + if (!this.children.isEmpty()) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_NO_CHILDREN, + ImmutableMap.of("type", TextComponent.of("SUBDIVISION").color(TextColor.AQUA))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + if (this.parent == null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_CREATE_DENY, + ImmutableMap.of( + "type", TextComponent.of("SUBDIVISION", TextColor.AQUA), + "target_type", TextComponent.of("WILDERNESS", TextColor.GREEN))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + if (this.isAdminClaim() && newOwnerUUID == null) { + return new GDClaimResult(ClaimResultType.REQUIRES_OWNER, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_REQUIRES_OWNER, + ImmutableMap.of( + "type", TextComponent.of("ADMIN", TextColor.RED), + "target_type", TextComponent.of("SUBDIVISION", TextColor.AQUA)))); + } + } else if (type == ClaimTypes.TOWN) { + if (this.parent != null && this.parent.isTown()) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_NO_CHILDREN, + ImmutableMap.of("type", TextComponent.of("TOWN").color(TextColor.GREEN))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + } else if (type == ClaimTypes.WILDERNESS) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.RESULT_TYPE_CHANGE_DENY, + ImmutableMap.of("type", TextComponent.of("WILDERNESS").color(TextColor.GREEN))); + return new GDClaimResult(ClaimResultType.WRONG_CLAIM_TYPE, message); + } + + return new GDClaimResult(ClaimResultType.SUCCESS); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || !(o instanceof GDClaim)) { + return false; + } + GDClaim that = (GDClaim) o; + return this.type == that.type && + Objects.equal(this.id, that.id); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public List<UUID> getUserTrusts() { + List<UUID> trustList = new ArrayList<>(); + trustList.addAll(this.claimData.getAccessors()); + trustList.addAll(this.claimData.getContainers()); + trustList.addAll(this.claimData.getBuilders()); + trustList.addAll(this.claimData.getManagers()); + return ImmutableList.copyOf(trustList); + } + + @Override + public List<UUID> getUserTrusts(TrustType type) { + return ImmutableList.copyOf(this.getUserTrustList(type)); + } + + public boolean isUserTrusted(User user, TrustType type) { + final GDPermissionUser gdUser = PermissionHolderCache.getInstance().getOrCreateUser(user); + return isUserTrusted(gdUser, type, null); + } + + public boolean isUserTrusted(GDPermissionUser user, TrustType type) { + return isUserTrusted(user, type, null); + } + + @Override + public boolean isUserTrusted(UUID uuid, TrustType type) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + return isUserTrusted(user, type, null); + } + + public boolean isUserTrusted(GDPermissionUser user, TrustType type, Set<Context> contexts) { + return isUserTrusted(user, type, contexts, false); + } + + public boolean isUserTrusted(GDPermissionUser user, TrustType type, Set<Context> contexts, boolean forced) { + if (user == null) { + return false; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.world.getUniqueId(), user.getUniqueId()); + if (!playerData.canIgnoreClaim(this) && this.getInternalClaimData().isExpired()) { + return false; + } + if (forced || !playerData.debugClaimPermissions) { + if (user.getUniqueId().equals(this.getOwnerUniqueId())) { + return true; + } + if (this.isAdminClaim() && playerData.canManageAdminClaims) { + return true; + } + if (this.isWilderness() && playerData.canManageWilderness) { + return true; + } + if (playerData.canIgnoreClaim(this)) { + return true; + } + } + + if (type == null) { + return true; + } + if (this.isPublicTrusted(type)) { + return true; + } + + if (type == TrustTypes.ACCESSOR) { + if (this.claimData.getAccessors().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getBuilders().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getContainers().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getManagers().contains(user.getUniqueId())) { + return true; + } + } else if (type == TrustTypes.BUILDER) { + if (this.claimData.getBuilders().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getManagers().contains(user.getUniqueId())) { + return true; + } + } else if (type == TrustTypes.CONTAINER) { + if (this.claimData.getContainers().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getBuilders().contains(user.getUniqueId())) { + return true; + } + if (this.claimData.getManagers().contains(user.getUniqueId())) { + return true; + } + } else if (type == TrustTypes.MANAGER) { + if (this.claimData.getManagers().contains(user.getUniqueId())) { + return true; + } + } + + if (contexts == null) { + contexts = new HashSet<>(); + contexts.add(this.getContext()); + } + + if (PermissionUtil.getInstance().getPermissionValue(this, user, GDPermissions.getTrustPermission(type), contexts) == Tristate.TRUE) { + return true; + } + + // Only check parent if this claim inherits + if (this.parent != null && this.getData().doesInheritParent()) { + return this.parent.isUserTrusted(user, type, contexts); + } + + return false; + } + + private boolean isPublicTrusted(TrustType type) { + if (type == TrustTypes.ACCESSOR) { + if (this.claimData.getAccessors().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getBuilders().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getContainers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getManagers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + } else if (type == TrustTypes.BUILDER) { + if (this.claimData.getBuilders().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getManagers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + } else if (type == TrustTypes.CONTAINER) { + if (this.claimData.getContainers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getBuilders().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + if (this.claimData.getManagers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + } else if (type == TrustTypes.MANAGER) { + if (this.claimData.getManagers().contains(GriefDefenderPlugin.PUBLIC_UUID)) { + return true; + } + } + + return false; + } + + @Override + public boolean isGroupTrusted(String name, TrustType type) { + if (name == null) { + return false; + } + + if (!PermissionUtil.getInstance().hasGroupSubject(name)) { + return false; + } + + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateHolder(name); + Set<Context> contexts = new HashSet<>(); + contexts.add(this.getContext()); + + return PermissionUtil.getInstance().getPermissionValue(this, holder, GDPermissions.getTrustPermission(type), contexts) == Tristate.TRUE; + } + + @Override + public ClaimResult addUserTrust(UUID uuid, TrustType type) { + GDUserTrustClaimEvent.Add event = new GDUserTrustClaimEvent.Add(this, ImmutableList.of(uuid), type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + List<UUID> userList = this.getUserTrustList(type); + if (!userList.contains(uuid)) { + userList.add(uuid); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult addUserTrusts(List<UUID> uuids, TrustType type) { + GDUserTrustClaimEvent.Add event = new GDUserTrustClaimEvent.Add(this, uuids, type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + for (UUID uuid : uuids) { + List<UUID> userList = this.getUserTrustList(type); + if (!userList.contains(uuid)) { + userList.add(uuid); + } + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeUserTrust(UUID uuid, TrustType type) { + GDUserTrustClaimEvent.Remove event = new GDUserTrustClaimEvent.Remove(this, ImmutableList.of(uuid), type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + if (type == TrustTypes.NONE) { + final ClaimResult result = this.removeAllTrustsFromUser(uuid); + this.claimData.setRequiresSave(true); + this.claimData.save(); + return result; + } + + this.getUserTrustList(type).remove(uuid); + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeUserTrusts(List<UUID> uuids, TrustType type) { + GDUserTrustClaimEvent.Remove event = new GDUserTrustClaimEvent.Remove(this, uuids, type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + if (type == TrustTypes.NONE) { + for (UUID uuid : uuids) { + this.removeAllTrustsFromUser(uuid); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + List<UUID> userList = this.getUserTrustList(type); + for (UUID uuid : uuids) { + if (userList.contains(uuid)) { + userList.remove(uuid); + } + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult addGroupTrust(String group, TrustType type) { + GDGroupTrustClaimEvent.Add event = new GDGroupTrustClaimEvent.Add(this, ImmutableList.of(group), type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + List<String> groupList = this.getGroupTrustList(type); + if (!groupList.contains(group)) { + groupList.add(group); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult addGroupTrusts(List<String> groups, TrustType type) { + GDGroupTrustClaimEvent.Add event = new GDGroupTrustClaimEvent.Add(this, groups, type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + for (String group : groups) { + List<String> groupList = this.getGroupTrustList(type); + if (!groupList.contains(group)) { + groupList.add(group); + } + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeGroupTrust(String group, TrustType type) { + GDGroupTrustClaimEvent.Remove event = new GDGroupTrustClaimEvent.Remove(this, ImmutableList.of(group), type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + if (type == TrustTypes.NONE) { + final ClaimResult result = this.removeAllTrustsFromGroup(group); + this.claimData.setRequiresSave(true); + this.claimData.save(); + return result; + } + + this.getGroupTrustList(type).remove(group); + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeGroupTrusts(List<String> groups, TrustType type) { + GDGroupTrustClaimEvent.Remove event = new GDGroupTrustClaimEvent.Remove(this, groups, type); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + if (type == TrustTypes.NONE) { + for (String group : groups) { + this.removeAllTrustsFromGroup(group); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + List<String> groupList = this.getGroupTrustList(type); + for (String group : groups) { + if (groupList.contains(group)) { + groupList.remove(group); + } + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeAllTrusts() { + List<UUID> userTrustList = this.getUserTrusts(); + GDUserTrustClaimEvent.Remove userEvent = new GDUserTrustClaimEvent.Remove(this, userTrustList, TrustTypes.NONE); + GriefDefender.getEventManager().post(userEvent); + if (userEvent.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, userEvent.getMessage().orElse(null)); + } + + List<String> groupTrustList = this.getGroupTrusts(); + GDGroupTrustClaimEvent.Remove event = new GDGroupTrustClaimEvent.Remove(this, groupTrustList, TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getUserTrustList(type).clear(); + } + + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getGroupTrustList(type).clear(); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeAllUserTrusts() { + List<UUID> trustList = this.getUserTrusts(); + GDUserTrustClaimEvent.Remove event = new GDUserTrustClaimEvent.Remove(this, trustList, TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getUserTrustList(type).clear(); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + @Override + public ClaimResult removeAllGroupTrusts() { + List<String> trustList = this.getGroupTrusts(); + GDGroupTrustClaimEvent.Remove event = new GDGroupTrustClaimEvent.Remove(this, trustList, TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getGroupTrustList(type).clear(); + } + + this.claimData.setRequiresSave(true); + this.claimData.save(); + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + public ClaimResult removeAllTrustsFromUser(UUID userUniqueId) { + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getUserTrustList(type).remove(userUniqueId); + } + + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + public ClaimResult removeAllTrustsFromGroup(String group) { + for (TrustType type : TrustTypeRegistryModule.getInstance().getAll()) { + this.getGroupTrustList(type).remove(group); + } + + return new GDClaimResult(this, ClaimResultType.SUCCESS); + } + + public List<UUID> getUserTrustList(TrustType type) { + if (type == TrustTypes.NONE) { + return new ArrayList<>(); + } + if (type == TrustTypes.ACCESSOR) { + return this.claimData.getAccessors(); + } + if (type == TrustTypes.CONTAINER) { + return this.claimData.getContainers(); + } + if (type == TrustTypes.BUILDER) { + return this.claimData.getBuilders(); + } + return this.claimData.getManagers(); + } + + public List<UUID> getParentUserTrustList(TrustType type) { + List<UUID> userList = new ArrayList<>(); + for (Claim claim : this.getInheritedParents()) { + GDClaim parentClaim = (GDClaim) claim; + userList.addAll(parentClaim.getUserTrusts(type)); + } + return userList; + } + + public List<String> getParentGroupTrustList(TrustType type) { + List<String> trustList = new ArrayList<>(); + for (Claim claim : this.getInheritedParents()) { + GDClaim parentClaim = (GDClaim) claim; + trustList.addAll(parentClaim.getGroupTrusts(type)); + } + return trustList; + } + + public List<UUID> getUserTrustList(TrustType type, boolean includeParents) { + List<UUID> trustList = new ArrayList<>(); + if (type == TrustTypes.ACCESSOR) { + trustList.addAll(this.claimData.getAccessors()); + } else if (type == TrustTypes.CONTAINER) { + trustList.addAll(this.claimData.getContainers()); + } else if (type == TrustTypes.BUILDER) { + trustList.addAll(this.claimData.getBuilders()); + } else { + trustList.addAll(this.claimData.getManagers()); + } + + if (includeParents) { + List<UUID> parentList = getParentUserTrustList(type); + for (UUID uuid : parentList) { + if (!trustList.contains(uuid)) { + trustList.add(uuid); + } + } + } + + return trustList; + } + + public List<String> getGroupTrustList(TrustType type) { + if (type == TrustTypes.NONE) { + return new ArrayList<>(); + } + if (type == TrustTypes.ACCESSOR) { + return this.claimData.getAccessorGroups(); + } + if (type == TrustTypes.CONTAINER) { + return this.claimData.getContainerGroups(); + } + if (type == TrustTypes.BUILDER) { + return this.claimData.getBuilderGroups(); + } + return this.claimData.getManagerGroups(); + } + + public List<String> getGroupTrustList(TrustType type, boolean includeParents) { + List<String> trustList = new ArrayList<>(); + if (type == TrustTypes.ACCESSOR) { + trustList.addAll(this.claimData.getAccessorGroups()); + } else if (type == TrustTypes.CONTAINER) { + trustList.addAll(this.claimData.getContainerGroups()); + } else if (type == TrustTypes.BUILDER) { + trustList.addAll(this.claimData.getBuilderGroups()); + } else { + trustList.addAll(this.claimData.getManagerGroups()); + } + + if (includeParents) { + List<String> parentList = getParentGroupTrustList(type); + for (String groupId : parentList) { + if (!trustList.contains(groupId)) { + trustList.add(groupId); + } + } + } + + return trustList; + } + + @Override + public List<String> getGroupTrusts() { + List<String> groups = new ArrayList<>(); + groups.addAll(this.getInternalClaimData().getAccessorGroups()); + groups.addAll(this.getInternalClaimData().getBuilderGroups()); + groups.addAll(this.getInternalClaimData().getContainerGroups()); + groups.addAll(this.getInternalClaimData().getManagerGroups()); + return ImmutableList.copyOf(groups); + } + + @Override + public List<String> getGroupTrusts(TrustType type) { + return ImmutableList.copyOf(this.getGroupTrustList(type)); + } + + @Override + public Optional<UUID> getEconomyAccountId() { + if (this.isAdminClaim() || this.isSubdivision() || !GriefDefenderPlugin.getGlobalConfig().getConfig().claim.bankTaxSystem) { + return Optional.empty(); + } + + if (this.economyAccount != null) { + return Optional.of(this.id); + } + EconomyService economyService = GriefDefenderPlugin.getInstance().economyService.orElse(null); + if (economyService != null) { + this.economyAccount = economyService.getOrCreateAccount(this.id.toString()).orElse(null); + return Optional.ofNullable(this.id); + } + return Optional.empty(); + } + + public Account getEconomyAccount() { + if (this.isAdminClaim() || this.isSubdivision() || !GriefDefenderPlugin.getGlobalConfig().getConfig().claim.bankTaxSystem) { + return null; + } + + if (this.economyAccount != null) { + return this.economyAccount; + } + EconomyService economyService = GriefDefenderPlugin.getInstance().economyService.orElse(null); + if (economyService != null) { + this.economyAccount = economyService.getOrCreateAccount(this.id.toString()).orElse(null); + return this.economyAccount; + } + + return null; + } + + public static class ClaimBuilder implements Builder { + + private UUID ownerUniqueId; + private ClaimType type = ClaimTypes.BASIC; + private boolean cuboid = false; + private boolean requiresClaimBlocks = true; + private boolean denyMessages = true; + private boolean expire = true; + private boolean resizable = true; + private boolean inherit = true; + private boolean overrides = true; + private boolean createLimitRestrictions = true; + private boolean levelRestrictions = true; + private boolean sizeRestrictions = true; + private UUID worldUniqueId; + private Vector3i point1; + private Vector3i point2; + private Vector3i spawnPos; + private Component greeting; + private Component farewell; + private Claim parent; + + public ClaimBuilder() { + + } + + @Override + public Builder bounds(Vector3i point1, Vector3i point2) { + this.point1 = point1; + this.point2 = point2; + return this; + } + + @Override + public Builder cuboid(boolean cuboid) { + this.cuboid = cuboid; + return this; + } + + @Override + public Builder owner(UUID ownerUniqueId) { + this.ownerUniqueId = ownerUniqueId; + return this; + } + + @Override + public Builder parent(Claim parentClaim) { + this.parent = parentClaim; + return this; + } + + @Override + public Builder type(ClaimType type) { + this.type = type; + return this; + } + + @Override + public Builder world(UUID worldUniqueId) { + this.worldUniqueId = worldUniqueId; + return this; + } + + @Override + public Builder createLimitRestrictions(boolean checkCreateLimit) { + this.createLimitRestrictions = checkCreateLimit; + return this; + } + + @Override + public Builder levelRestrictions(boolean checkLevel) { + this.levelRestrictions = checkLevel; + return this; + } + + @Override + public Builder sizeRestrictions(boolean checkSize) { + this.sizeRestrictions = checkSize; + return this; + } + + @Override + public Builder requireClaimBlocks(boolean requiresClaimBlocks) { + this.requiresClaimBlocks = requiresClaimBlocks; + return this; + } + + @Override + public Builder denyMessages(boolean allowDenyMessages) { + this.denyMessages = allowDenyMessages; + return this; + } + + @Override + public Builder expire(boolean allowExpire) { + this.expire = allowExpire; + return this; + } + + @Override + public Builder inherit(boolean inherit) { + this.inherit = inherit; + return this; + } + + @Override + public Builder resizable(boolean allowResize) { + this.resizable = allowResize; + return this; + } + + @Override + public Builder overrides(boolean allowOverrides) { + this.overrides = allowOverrides; + return this; + } + + @Override + public Builder farewell(Component farewell) { + this.farewell = farewell; + return this; + } + + @Override + public Builder greeting(Component greeting) { + this.greeting = greeting; + return this; + } + + @Override + public Builder spawnPos(Vector3i spawnPos) { + this.spawnPos = spawnPos; + return this; + } + + @Override + public Builder reset() { + this.ownerUniqueId = null; + this.type = ClaimTypes.BASIC; + this.cuboid = false; + this.worldUniqueId = null; + this.point1 = null; + this.point2 = null; + this.parent = null; + return this; + } + + @Override + public ClaimResult build() { + checkNotNull(this.type); + checkNotNull(this.worldUniqueId); + checkNotNull(this.point1); + checkNotNull(this.point2); + + final World world = Sponge.getServer().getWorld(this.worldUniqueId).orElse(null); + if (world == null) { + return new GDClaimResult(ClaimResultType.WORLD_NOT_FOUND); + } + + if (this.type == ClaimTypes.SUBDIVISION) { + checkNotNull(this.parent); + } + + if (this.type == ClaimTypes.ADMIN || this.type == ClaimTypes.WILDERNESS) { + this.sizeRestrictions = false; + this.levelRestrictions = false; + } + + GDClaim claim = null; + if (this.type == ClaimTypes.TOWN) { + claim = new GDTown(world, this.point1, this.point2, this.type, this.ownerUniqueId, this.cuboid); + } else { + claim = new GDClaim(world, this.point1, this.point2, this.type, this.ownerUniqueId, this.cuboid); + } + claim.parent = (GDClaim) this.parent; + Player player = null; + final Object source = GDCauseStackManager.getInstance().getCurrentCause().root(); + if (source instanceof GDPermissionUser) { + final GDPermissionUser user = (GDPermissionUser) source; + player = (Player) user.getOnlinePlayer(); + } + GDPlayerData playerData = null; + double requiredFunds = 0; + + if (this.ownerUniqueId != null) { + if (playerData == null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.worldUniqueId, this.ownerUniqueId); + } + + if (this.levelRestrictions) { + final int minClaimLevel = claim.getOwnerMinClaimLevel(); + if (claim.getLesserBoundaryCorner().getY() < minClaimLevel) { + Component message = null; + if (player != null) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_BELOW_LEVEL, ImmutableMap.of( + "limit", minClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(claim, ClaimResultType.BELOW_MIN_LEVEL, message); + } + final int maxClaimLevel = claim.getOwnerMaxClaimLevel(); + if (claim.getGreaterBoundaryCorner().getY() > maxClaimLevel) { + Component message = null; + if (player != null) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_ABOVE_LEVEL, ImmutableMap.of( + "limit", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(claim, ClaimResultType.ABOVE_MAX_LEVEL, message); + } + } + + if (this.sizeRestrictions) { + ClaimResult claimResult = claim.checkSizeLimits(player, playerData, this.point1, this.point2); + if (!claimResult.successful()) { + return claimResult; + } + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(this.ownerUniqueId); + if (this.createLimitRestrictions && !PermissionUtil.getInstance().holderHasPermission(user, GDPermissions.BYPASS_CLAIM_LIMIT)) { + final int createClaimLimit = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.CREATE_LIMIT, claim); + if (createClaimLimit > -1 && (playerData.getClaimTypeCount(claim.getType()) + 1) > createClaimLimit) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_FAILED_CLAIM_LIMIT, ImmutableMap.of( + "limit", createClaimLimit, + "type", claim.getType().getName())); + if (player != null) { + GriefDefenderPlugin.sendMessage(player, message); + } + return new GDClaimResult(claim, ClaimResultType.EXCEEDS_MAX_CLAIM_LIMIT, message); + } + } + + // check player has enough claim blocks + if ((claim.isBasicClaim() || claim.isTown()) && this.requiresClaimBlocks) { + final int claimCost = BlockUtil.getInstance().getClaimBlockCost(world, claim.lesserBoundaryCorner, claim.greaterBoundaryCorner, claim.cuboid); + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final GDClaimResult result = EconomyUtil.getInstance().checkEconomyFunds(claim, playerData, true); + if (!result.successful()) { + return result; + } + requiredFunds = claimCost * claim.getOwnerEconomyBlockCost(); + } else { + final int remainingClaimBlocks = playerData.getRemainingClaimBlocks() - claimCost; + if (remainingClaimBlocks < 0) { + Component message = null; + if (player != null) { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = Math.abs(remainingClaimBlocks / 65536.0); + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_SIZE_NEED_BLOCKS_3D, ImmutableMap.of( + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", Math.abs(remainingClaimBlocks))); + } else { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_SIZE_NEED_BLOCKS_2D, ImmutableMap.of( + "block-amount", Math.abs(remainingClaimBlocks))); + } + GriefDefenderPlugin.sendMessage(player, message); + } + //playerData.lastShovelLocation = null; + playerData.claimResizing = null; + return new GDClaimResult(ClaimResultType.INSUFFICIENT_CLAIM_BLOCKS, message); + } + } + } + + if (!GriefDefenderPlugin.getInstance().isEconomyModeEnabled() && claim.isTown() && player != null) { + final double townCost = GriefDefenderPlugin.getGlobalConfig().getConfig().town.cost; + if (townCost > 0) { + Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return new GDClaimResult(claim, ClaimResultType.NOT_ENOUGH_FUNDS, message); + } + final double balance = playerAccount.getBalance(GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency()).doubleValue(); + if (balance < townCost) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TOWN_CREATE_NOT_ENOUGH_FUNDS, ImmutableMap.of( + "amount", String.valueOf("$" +townCost), + "balance", String.valueOf("$" + balance), + "amount-needed", String.valueOf("$" + (townCost - balance)))); + GriefDefenderPlugin.sendMessage(player, message); + return new GDClaimResult(claim, ClaimResultType.NOT_ENOUGH_FUNDS, message); + } + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + playerAccount.withdraw(defaultCurrency, BigDecimal.valueOf(townCost), Cause.of(EventContext.empty(), GDCauseStackManager.getInstance().getCurrentCause().all())); + } + } + } + + final ClaimResult claimResult = claim.checkArea(false); + if (!claimResult.successful()) { + if (player != null && (claim.isBasicClaim() || claim.isTown()) && this.requiresClaimBlocks && GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + EconomyUtil.depositFunds(player.getUniqueId(), requiredFunds); + } + return claimResult; + } + + GDCreateClaimEvent.Pre event = new GDCreateClaimEvent.Pre(claim); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + final Component message = event.getMessage().orElse(null); + if (message != null && player != null) { + GriefDefenderPlugin.sendMessage(player, message); + } + if (player != null && (claim.isBasicClaim() || claim.isTown()) && this.requiresClaimBlocks && GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + EconomyUtil.depositFunds(player.getUniqueId(), requiredFunds); + } + return new GDClaimResult(claim, ClaimResultType.CLAIM_EVENT_CANCELLED, message); + } + + claim.initializeClaimData((GDClaim) this.parent); + if (this.parent != null) { + if (this.parent.isTown()) { + claim.getData().setInheritParent(true); + } + claim.getData().setParent(this.parent.getUniqueId()); + } + + claim.getData().setExpiration(this.expire); + claim.getData().setDenyMessages(this.denyMessages); + claim.getData().setFlagOverrides(this.overrides); + claim.getData().setInheritParent(this.inherit); + claim.getData().setResizable(this.resizable); + claim.getData().setRequiresClaimBlocks(this.requiresClaimBlocks); + claim.getData().setFarewell(this.farewell); + claim.getData().setGreeting(this.greeting); + claim.getData().setSpawnPos(this.spawnPos); + claim.getData().setSizeRestrictions(this.sizeRestrictions); + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.worldUniqueId); + claimManager.addClaim(claim, true); + + if (claimResult.getClaims().size() > 1) { + claim.migrateClaims(new ArrayList<>(claimResult.getClaims())); + } + + GDCreateClaimEvent.Post postEvent = new GDCreateClaimEvent.Post(claim); + GriefDefender.getEventManager().post(postEvent); + if (postEvent.cancelled()) { + final Component message = postEvent.getMessage().orElse(null); + if (message != null && player != null) { + GriefDefenderPlugin.sendMessage(player, message); + } + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + claimWorldManager.deleteClaimInternal(claim, true); + return new GDClaimResult(claim, ClaimResultType.CLAIM_EVENT_CANCELLED, message); + } + + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + if (GriefDefenderPlugin.getActiveConfig(this.worldUniqueId).getConfig().claim.claimAutoSchematicRestore) { + final ClaimSchematic schematic = ClaimSchematic.builder().claim(claim).name("__restore__").build().orElse(null); + } + } + return new GDClaimResult(claim, ClaimResultType.SUCCESS); + } + } + + public boolean migrateClaims(List<Claim> claims) { + GDClaim parentClaim = this; + for (Claim child : claims) { + if (child.equals(this)) { + continue; + } + + moveChildToParent(parentClaim, (GDClaim) child); + } + + return true; + } + + public void moveChildToParent(GDClaim parentClaim, GDClaim childClaim) { + // Remove child from current parent if available + if (childClaim.parent != null && childClaim.parent != parentClaim) { + childClaim.parent.children.remove(childClaim); + } + childClaim.parent = parentClaim; + String fileName = childClaim.getClaimStorage().filePath.getFileName().toString(); + Path newPath = parentClaim.getClaimStorage().folderPath.resolve(childClaim.getType().getName().toLowerCase()).resolve(fileName); + try { + if (Files.notExists(newPath.getParent())) { + Files.createDirectories(newPath.getParent()); + } + Files.move(childClaim.getClaimStorage().filePath, newPath); + if (childClaim.getClaimStorage().folderPath.toFile().listFiles().length == 0) { + Files.delete(childClaim.getClaimStorage().folderPath); + } + childClaim.setClaimStorage(new ClaimStorageData(newPath, this.getWorldUniqueId(), (ClaimDataConfig) childClaim.getInternalClaimData())); + } catch (IOException e) { + e.printStackTrace(); + } + // Make sure to update new parent in storage + childClaim.getInternalClaimData().setParent(parentClaim.getUniqueId()); + this.worldClaimManager.addClaim(childClaim, true); + for (Claim child : childClaim.children) { + moveChildToParent(childClaim, (GDClaim) child); + } + } + + @Override + public Context getDefaultTypeContext() { + if (this.isAdminClaim()) { + return ClaimContexts.ADMIN_DEFAULT_CONTEXT; + } + if (this.isBasicClaim() || this.isSubdivision()) { + return ClaimContexts.BASIC_DEFAULT_CONTEXT; + } + if (this.isTown()) { + return ClaimContexts.TOWN_DEFAULT_CONTEXT; + } + + return ClaimContexts.WILDERNESS_DEFAULT_CONTEXT; + } + + public org.spongepowered.api.service.context.Context getSpongeDefaultTypeContext() { + if (this.isAdminClaim()) { + return SpongeContexts.ADMIN_DEFAULT_CONTEXT; + } + if (this.isBasicClaim() || this.isSubdivision()) { + return SpongeContexts.BASIC_DEFAULT_CONTEXT; + } + if (this.isTown()) { + return SpongeContexts.TOWN_DEFAULT_CONTEXT; + } + + return SpongeContexts.WILDERNESS_DEFAULT_CONTEXT; + } + + @Override + public Context getOverrideTypeContext() { + if (this.isAdminClaim()) { + return ClaimContexts.ADMIN_OVERRIDE_CONTEXT; + } + if (this.isBasicClaim() || this.isSubdivision()) { + return ClaimContexts.BASIC_OVERRIDE_CONTEXT; + } + if (this.isTown()) { + return ClaimContexts.TOWN_OVERRIDE_CONTEXT; + } + + return ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT; + } + + @Override + public Context getOverrideClaimContext() { + return this.overrideClaimContext; + } + + public org.spongepowered.api.service.context.Context getSpongeOverrideTypeContext() { + if (this.isAdminClaim()) { + return SpongeContexts.ADMIN_OVERRIDE_CONTEXT; + } + if (this.isBasicClaim() || this.isSubdivision()) { + return SpongeContexts.BASIC_OVERRIDE_CONTEXT; + } + if (this.isTown()) { + return SpongeContexts.TOWN_OVERRIDE_CONTEXT; + } + + return SpongeContexts.WILDERNESS_OVERRIDE_CONTEXT; + } + + public org.spongepowered.api.service.context.Context getSpongeOverrideClaimContext() { + return this.spongeOverrideClaimContext; + } + + @Override + public Map<String, ClaimSchematic> getSchematics() { + return this.schematics; + } + + @Override + public boolean deleteSchematic(String name) { + final Path schematicPath = GriefDefenderPlugin.getInstance().getWorldEditProvider().getSchematicWorldMap().get(this.world.getUniqueId()).resolve(this.id.toString()); + try { + Files.createDirectories(schematicPath); + } catch (IOException e1) { + e1.printStackTrace(); + } + File outputFile = schematicPath.resolve(name + ".schematic").toFile(); + if (outputFile.delete()) { + this.schematics.remove(name); + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/claim/GDClaimManager.java b/sponge/src/main/java/com/griefdefender/claim/GDClaimManager.java new file mode 100644 index 0000000..2eb21b6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDClaimManager.java @@ -0,0 +1,668 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimManager; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.configuration.ClaimDataConfig; +import com.griefdefender.configuration.ClaimStorageData; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.PlayerStorageData; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.storage.BaseStorage; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.account.UniqueAccount; +import org.spongepowered.api.util.Direction; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +public class GDClaimManager implements ClaimManager { + + private static final BaseStorage DATASTORE = GriefDefenderPlugin.getInstance().dataStore; + private UUID worldUniqueId; + private GriefDefenderConfig<?> activeConfig; + + // Player UUID -> player data + private Map<UUID, GDPlayerData> playerDataList = Maps.newHashMap(); + // World claim list + private Set<Claim> worldClaims = new HashSet<>(); + // Claim UUID -> Claim + private Map<UUID, Claim> claimUniqueIdMap = Maps.newHashMap(); + // String -> Claim + private Map<Long, Set<Claim>> chunksToClaimsMap = new Long2ObjectOpenHashMap<>(4096); + private GDClaim theWildernessClaim; + + public GDClaimManager(World world) { + this.worldUniqueId = world.getUniqueId(); + this.activeConfig = GriefDefenderPlugin.getActiveConfig(this.worldUniqueId); + } + + public GDPlayerData getOrCreatePlayerData(UUID playerUniqueId) { + GDPlayerData playerData = this.getPlayerDataMap().get(playerUniqueId); + if (playerData == null) { + return createPlayerData(playerUniqueId); + } else { + return playerData; + } + } + + private GDPlayerData createPlayerData(UUID playerUniqueId) { + Path playerFilePath = null; + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + playerFilePath = BaseStorage.globalPlayerDataPath.resolve(playerUniqueId.toString()); + } else { + playerFilePath = BaseStorage.worldConfigMap.get(this.worldUniqueId).getPath().getParent().resolve("PlayerData").resolve(playerUniqueId.toString()); + } + + PlayerStorageData playerStorage = new PlayerStorageData(playerFilePath); + Set<Claim> claimList = this.createPlayerClaimList(playerUniqueId); + GDPlayerData playerData = new GDPlayerData(this.worldUniqueId, playerUniqueId, playerStorage, this.activeConfig, claimList); + this.getPlayerDataMap().put(playerUniqueId, playerData); + return playerData; + } + + private Set<Claim> createPlayerClaimList(UUID playerUniqueId) { + Set<Claim> claimList = new HashSet<>(); + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + for (World world : Sponge.getServer().getWorlds()) { + GDClaimManager claimmanager = DATASTORE.getClaimWorldManager(world.getUniqueId()); + for (Claim claim : claimmanager.worldClaims) { + GDClaim gpClaim = (GDClaim) claim; + if (gpClaim.isAdminClaim()) { + continue; + } + if (gpClaim.parent != null) { + if (gpClaim.parent.getOwnerUniqueId().equals(playerUniqueId)) { + claimList.add(claim); + } + } else { + if (gpClaim.getOwnerUniqueId().equals(playerUniqueId)) { + claimList.add(claim); + } + } + } + } + } else { + for (Claim claim : this.worldClaims) { + GDClaim gpClaim = (GDClaim) claim; + if (gpClaim.isAdminClaim()) { + continue; + } + if (gpClaim.parent != null) { + if (gpClaim.parent.getOwnerUniqueId().equals(playerUniqueId)) { + claimList.add(claim); + } + } else { + if (gpClaim.getOwnerUniqueId().equals(playerUniqueId)) { + claimList.add(claim); + } + } + } + } + + return claimList; + } + + public void removePlayer(UUID playerUniqueId) { + this.getPlayerDataMap().remove(playerUniqueId); + } + + public ClaimResult addClaim(Claim claim) { + GDClaim newClaim = (GDClaim) claim; + // ensure this new claim won't overlap any existing claims + ClaimResult result = newClaim.checkArea(false); + if (!result.successful()) { + return result; + } + + // validate world + if (!this.worldUniqueId.equals(newClaim.getWorld().getUniqueId())) { + World world = Sponge.getServer().getWorld(this.worldUniqueId).get(); + newClaim.setWorld(world); + } + + // otherwise add this new claim to the data store to make it effective + this.addClaim(newClaim, true); + if (result.getClaims().size() > 1) { + newClaim.migrateClaims(new ArrayList<>(result.getClaims())); + } + return result; + } + + public void addClaim(Claim claimToAdd, boolean writeToStorage) { + GDClaim claim = (GDClaim) claimToAdd; + if (claim.parent == null && this.worldClaims.contains(claimToAdd)) { + return; + } + + if (writeToStorage) { + DATASTORE.writeClaimToStorage(claim); + } + + // We need to keep track of all claims so they can be referenced by children during server startup + this.claimUniqueIdMap.put(claim.getUniqueId(), claim); + + if (claim.isWilderness()) { + this.theWildernessClaim = claim; + return; + } + + if (claim.parent != null) { + claim.parent.children.add(claim); + this.worldClaims.remove(claim); + this.deleteChunkHashes((GDClaim) claim); + if (!claim.isAdminClaim() && (!claim.isInTown() || !claim.getTownClaim().getOwnerUniqueId().equals(claim.getOwnerUniqueId()))) { + final GDPlayerData playerData = this.getPlayerDataMap().get(claim.getOwnerUniqueId()); + Set<Claim> playerClaims = playerData.getInternalClaims(); + if (!playerClaims.contains(claim)) { + playerClaims.add(claim); + } + } + return; + } + + if (!this.worldClaims.contains(claim)) { + this.worldClaims.add(claim); + } + final UUID ownerId = claim.getOwnerUniqueId(); + final GDPlayerData playerData = this.getPlayerDataMap().get(ownerId); + if (playerData != null) { + Set<Claim> playerClaims = playerData.getInternalClaims(); + if (!playerClaims.contains(claim)) { + playerClaims.add(claim); + } + } else if (!claim.isAdminClaim()) { + this.createPlayerData(ownerId); + } + + this.updateChunkHashes(claim); + return; + } + + public void updateChunkHashes(GDClaim claim) { + this.deleteChunkHashes(claim); + Set<Long> chunkHashes = claim.getChunkHashes(true); + for (Long chunkHash : chunkHashes) { + Set<Claim> claimsInChunk = this.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk == null) { + claimsInChunk = new HashSet<Claim>(); + this.getInternalChunksToClaimsMap().put(chunkHash, claimsInChunk); + } + + claimsInChunk.add(claim); + } + } + + // Used when parent claims becomes children + public void removeClaimData(Claim claim) { + this.worldClaims.remove(claim); + this.deleteChunkHashes((GDClaim) claim); + } + + @Override + public ClaimResult deleteClaim(Claim claim, boolean deleteChildren) { + GDRemoveClaimEvent event = new GDRemoveClaimEvent(claim); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return new GDClaimResult(claim, ClaimResultType.CLAIM_EVENT_CANCELLED, event.getMessage().orElse(null)); + } + + return this.deleteClaimInternal(claim, deleteChildren); + } + + public ClaimResult deleteClaimInternal(Claim claim, boolean deleteChildren) { + final GDClaim gpClaim = (GDClaim) claim; + Set<Claim> subClaims = claim.getChildren(false); + for (Claim child : subClaims) { + if (deleteChildren || (gpClaim.parent == null && child.isSubdivision())) { + this.deleteClaimInternal(child, true); + continue; + } + + final GDClaim parentClaim = (GDClaim) claim; + final GDClaim childClaim = (GDClaim) child; + if (parentClaim.parent != null) { + migrateChildToNewParent(parentClaim.parent, childClaim); + } else { + // move child to parent folder + migrateChildToNewParent(null, childClaim); + } + } + + resetPlayerClaimVisuals(claim); + // transfer bank balance to owner + final Account bankAccount = ((GDClaim) claim).getEconomyAccount(); + if (bankAccount != null) { + try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + Sponge.getCauseStackManager().pushCause(GriefDefenderPlugin.getInstance()); + final EconomyService economyService = GriefDefenderPlugin.getInstance().economyService.get(); + final UniqueAccount ownerAccount = economyService.getOrCreateAccount(claim.getOwnerUniqueId()).orElse(null); + if (ownerAccount != null) { + ownerAccount.deposit(economyService.getDefaultCurrency(), bankAccount.getBalance(economyService.getDefaultCurrency()), + Sponge.getCauseStackManager().getCurrentCause()); + } + bankAccount.resetBalance(economyService.getDefaultCurrency(), Sponge.getCauseStackManager().getCurrentCause()); + } + } + this.worldClaims.remove(claim); + this.claimUniqueIdMap.remove(claim.getUniqueId()); + this.deleteChunkHashes((GDClaim) claim); + if (gpClaim.parent != null) { + gpClaim.parent.children.remove(claim); + } + + return DATASTORE.deleteClaimFromStorage((GDClaim) claim); + } + + // Migrates children to new parent + private void migrateChildToNewParent(GDClaim parentClaim, GDClaim childClaim) { + childClaim.parent = parentClaim; + String fileName = childClaim.getClaimStorage().filePath.getFileName().toString(); + Path newPath = null; + if (parentClaim == null) { + newPath = childClaim.getClaimStorage().folderPath.getParent().getParent().resolve(childClaim.getType().getName().toLowerCase()).resolve(fileName); + } else { + // Only store in same claim type folder if not admin. + // Admin claims are currently the only type that can hold children of same type within + if (childClaim.getType().equals(parentClaim.getType()) && (!parentClaim.isAdminClaim())) { + newPath = parentClaim.getClaimStorage().folderPath.resolve(fileName); + } else { + newPath = parentClaim.getClaimStorage().folderPath.resolve(childClaim.getType().getName().toLowerCase()).resolve(fileName); + } + } + + try { + if (Files.notExists(newPath.getParent())) { + Files.createDirectories(newPath.getParent()); + } + Files.move(childClaim.getClaimStorage().filePath, newPath); + if (childClaim.getClaimStorage().folderPath.toFile().listFiles().length == 0) { + Files.delete(childClaim.getClaimStorage().folderPath); + } + childClaim.setClaimStorage(new ClaimStorageData(newPath, this.worldUniqueId, (ClaimDataConfig) childClaim.getInternalClaimData())); + } catch (IOException e) { + e.printStackTrace(); + } + + // Make sure to update new parent in storage + final UUID parentUniqueId = parentClaim == null ? null : parentClaim.getUniqueId(); + childClaim.getInternalClaimData().setParent(parentUniqueId); + this.addClaim(childClaim, true); + for (Claim child : childClaim.children) { + migrateChildToNewParent(childClaim, (GDClaim) child); + } + } + + private void resetPlayerClaimVisuals(Claim claim) { + // player may be offline so check is needed + GDPlayerData playerData = this.getPlayerDataMap().get(claim.getOwnerUniqueId()); + if (playerData != null) { + playerData.getInternalClaims().remove(claim); + if (playerData.lastClaim != null) { + playerData.lastClaim.clear(); + } + } + + // revert visuals for all players watching this claim + List<UUID> playersWatching = new ArrayList<>(((GDClaim) claim).playersWatching); + for (UUID playerUniqueId : playersWatching) { + Player player = Sponge.getServer().getPlayer(playerUniqueId).orElse(null); + if (player != null) { + playerData = this.getOrCreatePlayerData(playerUniqueId); + playerData.revertActiveVisual(player); + if (playerData.lastClaim != null) { + playerData.lastClaim.clear(); + } + if (GriefDefenderPlugin.getInstance().worldEditProvider != null) { + GriefDefenderPlugin.getInstance().worldEditProvider.revertVisuals(player, playerData, claim.getUniqueId()); + } + } + } + } + + private void deleteChunkHashes(GDClaim claim) { + Set<Long> chunkHashes = claim.getChunkHashes(false); + if (chunkHashes == null) { + return; + } + + for (Long chunkHash : chunkHashes) { + Set<Claim> claimsInChunk = this.getInternalChunksToClaimsMap().get(chunkHash); + if (claimsInChunk != null) { + claimsInChunk.remove(claim); + } + } + } + + @Nullable + public Optional<Claim> getClaimByUUID(UUID claimUniqueId) { + return Optional.ofNullable(this.claimUniqueIdMap.get(claimUniqueId)); + } + + public Set<Claim> getInternalPlayerClaims(UUID playerUniqueId) { + final GDPlayerData playerData = this.getPlayerDataMap().get(playerUniqueId); + if (playerData == null) { + return new HashSet<>(); + } + return playerData.getInternalClaims(); + } + + @Nullable + public Set<Claim> getPlayerClaims(UUID playerUniqueId) { + final GDPlayerData playerData = this.getPlayerDataMap().get(playerUniqueId); + if (playerData == null) { + return ImmutableSet.of(); + } + return ImmutableSet.copyOf(this.getPlayerDataMap().get(playerUniqueId).getInternalClaims()); + } + + public void createWildernessClaim(World world) { + if (this.theWildernessClaim != null) { + return; + } + + final Vector3i lesserCorner = new Vector3i(-30000000, 0, -30000000); + final Vector3i greaterCorner = new Vector3i(29999999, 255, 29999999); + // Use world UUID as wilderness claim ID + GDClaim wilderness = new GDClaim(world, lesserCorner, greaterCorner, world.getUniqueId(), ClaimTypes.WILDERNESS, null, false); + wilderness.setOwnerUniqueId(GriefDefenderPlugin.WORLD_USER_UUID); + wilderness.initializeClaimData(null); + wilderness.claimData.save(); + wilderness.claimStorage.save(); + this.theWildernessClaim = wilderness; + this.claimUniqueIdMap.put(wilderness.getUniqueId(), wilderness); + } + + @Override + public GDClaim getWildernessClaim() { + if (this.theWildernessClaim == null) { + World world = Sponge.getServer().getWorld(this.worldUniqueId).get(); + this.createWildernessClaim(world); + } + return this.theWildernessClaim; + } + + @Override + public Set<Claim> getWorldClaims() { + return this.worldClaims; + } + + public Map<UUID, GDPlayerData> getPlayerDataMap() { + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + return BaseStorage.GLOBAL_PLAYER_DATA; + } + return this.playerDataList; + } + + public Set<Claim> findOverlappingClaims(Claim claim) { + Set<Claim> claimSet = new HashSet<>(); + for (Long chunkHash : claim.getChunkHashes()) { + final Set<Claim> chunkClaims = this.chunksToClaimsMap.get(chunkHash); + if (chunkClaims == null) { + continue; + } + for (Claim chunkClaim : chunkClaims) { + if (!chunkClaim.equals(claim) && (claim.overlaps(chunkClaim) || chunkClaim.overlaps(claim))) { + claimSet.add(chunkClaim); + } + } + } + return claimSet; + } + + @Override + public Map<Long, Set<Claim>> getChunksToClaimsMap() { + return ImmutableMap.copyOf(this.chunksToClaimsMap); + } + + public Map<Long, Set<Claim>> getInternalChunksToClaimsMap() { + return this.chunksToClaimsMap; + } + + public void save() { + for (Claim claim : this.worldClaims) { + GDClaim gpClaim = (GDClaim) claim; + gpClaim.save(); + } + this.getWildernessClaim().save(); + + for (GDPlayerData playerData : this.getPlayerDataMap().values()) { + playerData.getStorageData().save(); + } + } + + public void unload() { + this.playerDataList.clear(); + this.worldClaims.clear(); + this.claimUniqueIdMap.clear(); + this.chunksToClaimsMap.clear(); + if (this.theWildernessClaim != null) { + this.theWildernessClaim.unload(); + this.theWildernessClaim = null; + } + this.worldUniqueId = null; + } + + @Override + public Claim getClaimAt(Vector3i pos) { + final World world = Sponge.getServer().getWorld(this.worldUniqueId).orElse(null); + return this.getClaimAt(VecHelper.toLocation(world, pos), null, null, false); + } + + public Claim getClaimAt(Location<World> location, boolean useBorderBlockRadius) { + return this.getClaimAt(location, null, null, useBorderBlockRadius); + } + + public Claim getClaimAtPlayer(Location<World> location, GDPlayerData playerData) { + return this.getClaimAt(location, (GDClaim) playerData.lastClaim.get(), playerData, false); + } + + public Claim getClaimAtPlayer(Location<World> location, GDPlayerData playerData, boolean useBorderBlockRadius) { + return this.getClaimAt(location, (GDClaim) playerData.lastClaim.get(), playerData, useBorderBlockRadius); + } + + public Claim getClaimAt(Location<World> location) { + return this.getClaimAt(location, false); + } + + public Claim getClaimAt(Location<World> location, GDClaim cachedClaim, GDPlayerData playerData, boolean useBorderBlockRadius) { + //GPTimings.CLAIM_GETCLAIM.startTimingIfSync(); + // check cachedClaim guess first. if the location is inside it, we're done + if (cachedClaim != null && !cachedClaim.isWilderness() && cachedClaim.contains(location, true)) { + // GPTimings.CLAIM_GETCLAIM.stopTimingIfSync(); + return cachedClaim; + } + + Set<Claim> claimsInChunk = this.getInternalChunksToClaimsMap().get(BlockUtil.getInstance().asLong(location.getBlockX() >> 4, location.getBlockZ() >> 4)); + if (useBorderBlockRadius && (playerData != null && !playerData.bypassBorderCheck)) { + final int borderBlockRadius = GriefDefenderPlugin.getActiveConfig(location.getExtent().getUniqueId()).getConfig().claim.borderBlockRadius; + // if borderBlockRadius > 0, check surrounding chunks + if (borderBlockRadius > 0) { + for (Direction direction : BlockUtil.ORDINAL_SET) { + Location<World> currentLocation = location; + for (int i = 0; i < borderBlockRadius; i++) { // Handle depth + currentLocation = currentLocation.getBlockRelative(direction); + Set<Claim> relativeClaims = this.getInternalChunksToClaimsMap().get(BlockUtil.getInstance().asLong(currentLocation.getBlockX() >> 4, currentLocation.getBlockZ() >> 4)); + if (relativeClaims != null) { + if (claimsInChunk == null) { + claimsInChunk = new HashSet<>(); + } + claimsInChunk.addAll(relativeClaims); + } + } + } + } + } + if (claimsInChunk == null) { + //GPTimings.CLAIM_GETCLAIM.stopTimingIfSync(); + return this.getWildernessClaim(); + } + + for (Claim claim : claimsInChunk) { + GDClaim foundClaim = findClaim((GDClaim) claim, location, playerData, useBorderBlockRadius); + if (foundClaim != null) { + return foundClaim; + } + } + + //GPTimings.CLAIM_GETCLAIM.stopTimingIfSync(); + // if no claim found, return the world claim + return this.getWildernessClaim(); + } + + private GDClaim findClaim(GDClaim claim, Location<World> location, GDPlayerData playerData, boolean useBorderBlockRadius) { + if (claim.contains(location, playerData, useBorderBlockRadius)) { + // when we find a top level claim, if the location is in one of its children, + // return the child claim, not the top level claim + for (Claim childClaim : claim.children) { + GDClaim child = (GDClaim) childClaim; + if (!child.children.isEmpty()) { + GDClaim innerChild = findClaim(child, location, playerData, useBorderBlockRadius); + if (innerChild != null) { + return innerChild; + } + } + // check if child has children (Town -> Basic -> Subdivision) + if (child.contains(location, playerData, useBorderBlockRadius)) { + return child; + } + } + return claim; + } + return null; + } + + @Override + public List<Claim> getClaimsByName(String name) { + List<Claim> claimList = new ArrayList<>(); + for (Claim worldClaim : this.getWorldClaims()) { + Component claimName = worldClaim.getName().orElse(null); + if (claimName != null && claimName != TextComponent.empty()) { + if (PlainComponentSerializer.INSTANCE.serialize(claimName).equalsIgnoreCase(name)) { + claimList.add(worldClaim); + } + } + // check children + for (Claim child : ((GDClaim) worldClaim).getChildren(true)) { + if (child.getUniqueId().toString().equals(name)) { + claimList.add(child); + } + } + } + return claimList; + } + + public void resetPlayerData() { + // check migration reset + if (GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetMigrations) { + for (GDPlayerData playerData : this.getPlayerDataMap().values()) { + final PlayerStorageData playerStorage = playerData.getStorageData(); + playerStorage.getConfig().setMigratedBlocks(false); + playerStorage.save(); + } + } + // migrate playerdata to new claim block system + final int migration3dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateVolumeRate; + final int migration2dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateAreaRate; + final boolean resetClaimBlockData = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetAccruedClaimBlocks; + + if (migration3dRate <= -1 && migration2dRate <= -1 && !resetClaimBlockData) { + return; + } + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME && migration2dRate >= 0) { + return; + } + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.AREA && migration3dRate >= 0) { + return; + } + + for (GDPlayerData playerData : this.getPlayerDataMap().values()) { + final PlayerStorageData playerStorage = playerData.getStorageData(); + final int accruedBlocks = playerStorage.getConfig().getAccruedClaimBlocks(); + int newAccruedBlocks = accruedBlocks; + // first check reset + if (resetClaimBlockData) { + newAccruedBlocks = playerData.getTotalClaimsCost(); + playerStorage.getConfig().setBonusClaimBlocks(0); + } else if (migration3dRate > -1 && !playerStorage.getConfig().hasMigratedBlocks()) { + newAccruedBlocks = accruedBlocks * migration3dRate; + playerStorage.getConfig().setMigratedBlocks(true); + } else if (migration2dRate > -1 && !playerStorage.getConfig().hasMigratedBlocks()) { + newAccruedBlocks = accruedBlocks / migration2dRate; + playerStorage.getConfig().setMigratedBlocks(true); + } + if (newAccruedBlocks < 0) { + newAccruedBlocks = 0; + } + final int maxAccruedBlocks = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), playerData.getSubject(), Options.MAX_ACCRUED_BLOCKS); + if (newAccruedBlocks > maxAccruedBlocks) { + newAccruedBlocks = maxAccruedBlocks; + } + playerStorage.getConfig().setAccruedClaimBlocks(newAccruedBlocks); + playerStorage.save(); + } + } + + @Override + public UUID getWorldId() { + return this.worldUniqueId; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/claim/GDClaimResult.java b/sponge/src/main/java/com/griefdefender/claim/GDClaimResult.java new file mode 100644 index 0000000..e1817ce --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDClaimResult.java @@ -0,0 +1,89 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.google.common.collect.ImmutableList; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import net.kyori.text.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class GDClaimResult implements ClaimResult { + + private final Component eventMessage; + private final List<Claim> claims; + private final ClaimResultType resultType; + + public GDClaimResult(ClaimResultType type) { + this(type, null); + } + + public GDClaimResult(ClaimResultType type, Component message) { + this.claims = ImmutableList.of(); + this.resultType = type; + this.eventMessage = message; + } + + public GDClaimResult(Claim claim, ClaimResultType type) { + this(claim, type, null); + } + + public GDClaimResult(Claim claim, ClaimResultType type, Component message) { + List<Claim> claimList = new ArrayList<>(); + claimList.add(claim); + this.claims = ImmutableList.copyOf(claimList); + this.resultType = type; + this.eventMessage = message; + } + + public GDClaimResult(List<Claim> claims, ClaimResultType type) { + this(claims, type, null); + } + + public GDClaimResult(List<Claim> claims, ClaimResultType type, Component message) { + this.claims = ImmutableList.copyOf(claims); + this.resultType = type; + this.eventMessage = message; + } + + @Override + public ClaimResultType getResultType() { + return this.resultType; + } + + @Override + public Optional<Component> getMessage() { + return Optional.ofNullable(this.eventMessage); + } + + @Override + public List<Claim> getClaims() { + return this.claims; + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDClaimSchematic.java b/sponge/src/main/java/com/griefdefender/claim/GDClaimSchematic.java new file mode 100644 index 0000000..aa514a2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDClaimSchematic.java @@ -0,0 +1,187 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimSchematic; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.storage.FileStorage; +import org.spongepowered.api.data.DataContainer; +import org.spongepowered.api.data.persistence.DataFormats; +import org.spongepowered.api.data.persistence.DataTranslators; +import org.spongepowered.api.world.BlockChangeFlags; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.extent.ArchetypeVolume; +import org.spongepowered.api.world.schematic.Schematic; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Optional; +import java.util.zip.GZIPOutputStream; + +public class GDClaimSchematic implements ClaimSchematic { + + private Claim claim; + private Schematic schematic; + private String name; + private Vector3i origin; + private final Instant dateCreated; + + // Used during server startup to load schematic + public GDClaimSchematic(Claim claim, Schematic schematic, String name) { + this.claim = claim; + this.schematic = schematic; + this.name = name; + this.origin = claim.getLesserBoundaryCorner(); + this.dateCreated = Instant.now(); + } + + private GDClaimSchematic(Claim claim, Schematic schematic, String name, Vector3i origin) { + this.claim = claim; + this.schematic = schematic; + this.name = name; + this.origin = origin; + this.dateCreated = Instant.now(); + } + + @Override + public Claim getClaim() { + return this.claim; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Instant getDateCreated() { + return this.dateCreated; + } + + @Override + public Vector3i getOrigin() { + return this.origin; + } + + @Override + public void setOrigin(Vector3i pos) { + this.origin = pos; + } + + public Schematic getSchematic() { + return this.schematic; + } + + /** + * Applies schematic to claim. + * + * @return If schematic apply was successful, false if not + */ + public boolean apply() { + if (!this.schematic.containsBlock(this.claim.getLesserBoundaryCorner()) && !schematic.containsBlock(this.claim.getGreaterBoundaryCorner())) { + return false; + } + + this.schematic.apply(VecHelper.toLocation(((GDClaim)(this.claim)).getWorld(), this.claim.getLesserBoundaryCorner()), BlockChangeFlags.ALL); + return true; + } + + public static class ClaimSchematicBuilder implements Builder { + + private Claim claim; + private Schematic schematic; + private String name; + private Vector3i origin; + + @Override + public Builder claim(Claim claim) { + final World world = ((GDClaim) claim).getWorld(); + final ArchetypeVolume volume = world.createArchetypeVolume(this.claim.getLesserBoundaryCorner(), this.claim.getGreaterBoundaryCorner(), new Vector3i(0, 0, 0)); + final Schematic schematic = Schematic.builder() + .metaValue(Schematic.METADATA_NAME, name) + .metaValue(Schematic.METADATA_DATE, Instant.now().toString()) + .metaValue("UUID", this.claim.getUniqueId().toString()) + .volume(volume) + .build(); + this.claim = claim; + this.schematic = schematic; + return this; + } + + @Override + public Builder name(String name) { + this.name = name; + return this; + } + + @Override + public Builder origin(Vector3i origin) { + this.origin = origin; + return this; + } + + @Override + public Builder reset() { + this.name = null; + this.origin = null; + this.schematic = null; + return this; + } + + @Override + public Optional<ClaimSchematic> build() { + if (this.origin == null) { + //this.origin = new Vector3i(0, 0, 0); + this.origin = this.claim.getLesserBoundaryCorner(); + } + final World world = ((GDClaim) this.claim).getWorld(); + DataContainer schematicData = DataTranslators.SCHEMATIC.translate(schematic); + final Path schematicPath = GriefDefenderPlugin.getInstance().getWorldEditProvider().getSchematicWorldMap().get(world.getUniqueId()).resolve(this.claim.getUniqueId().toString()); + try { + Files.createDirectories(schematicPath); + } catch (IOException e1) { + e1.printStackTrace(); + } + File outputFile = schematicPath.resolve(name + ".schematic").toFile(); + try { + DataFormats.NBT.writeTo(new GZIPOutputStream(new FileOutputStream(outputFile)), schematicData); + } catch (Exception e) { + e.printStackTrace(); + return Optional.empty(); + } + + final GDClaimSchematic schematic = new GDClaimSchematic(this.claim, this.schematic, this.name, this.origin); + ((GDClaim) this.claim).schematics.put(this.name, schematic); + return Optional.of(schematic); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDClaimType.java b/sponge/src/main/java/com/griefdefender/claim/GDClaimType.java new file mode 100644 index 0000000..a1c9db1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDClaimType.java @@ -0,0 +1,119 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.permission.Context; +import com.griefdefender.util.SpongeContexts; + +public class GDClaimType implements ClaimType { + + private final String id; + private final String name; + private final Context defaultContext; + private final Context overrideContext; + private final org.spongepowered.api.service.context.Context spongeDefaultContext; + private final org.spongepowered.api.service.context.Context spongeOverrideContext; + + public GDClaimType(String id, String name) { + this.id = id; + this.name = name; + + if (name.equalsIgnoreCase("any") || name.equalsIgnoreCase("global")) { + this.defaultContext = ClaimContexts.GLOBAL_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.GLOBAL_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.GLOBAL_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.GLOBAL_OVERRIDE_CONTEXT; + } else if (name.equalsIgnoreCase("admin")) { + this.defaultContext = ClaimContexts.ADMIN_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.ADMIN_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.GLOBAL_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.GLOBAL_OVERRIDE_CONTEXT; + } else if (name.equalsIgnoreCase("basic")) { + this.defaultContext = ClaimContexts.BASIC_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.BASIC_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.GLOBAL_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.GLOBAL_OVERRIDE_CONTEXT; + } else if (name.equalsIgnoreCase("subdivision")) { + this.defaultContext = ClaimContexts.SUBDIVISION_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.SUBDIVISION_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.SUBDIVISION_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.SUBDIVISION_OVERRIDE_CONTEXT; + } else if (name.equalsIgnoreCase("town")) { + this.defaultContext = ClaimContexts.TOWN_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.TOWN_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.TOWN_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.TOWN_OVERRIDE_CONTEXT; + } else if (name.equalsIgnoreCase("wilderness")) { + this.defaultContext = ClaimContexts.WILDERNESS_DEFAULT_CONTEXT; + this.overrideContext = ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT; + this.spongeDefaultContext = SpongeContexts.WILDERNESS_DEFAULT_CONTEXT; + this.spongeOverrideContext = SpongeContexts.WILDERNESS_OVERRIDE_CONTEXT; + } else { + this.defaultContext = new Context("gd_claim_default", name.toLowerCase()); + this.overrideContext = new Context("gd_claim_override", name.toLowerCase()); + this.spongeDefaultContext = new org.spongepowered.api.service.context.Context("gd_claim_default", name.toLowerCase()); + this.spongeOverrideContext = new org.spongepowered.api.service.context.Context("gd_claim_override", name.toLowerCase()); + } + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + public String toString() { + return this.id; + } + + @Override + public Context getContext() { + return this.defaultContext; + } + + @Override + public Context getDefaultContext() { + return this.defaultContext; + } + + @Override + public Context getOverrideContext() { + return this.overrideContext; + } + + public org.spongepowered.api.service.context.Context getSpongeDefaultContext() { + return this.spongeDefaultContext; + } + + public org.spongepowered.api.service.context.Context getSpongeOverrideContext() { + return this.spongeOverrideContext; + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDShovelType.java b/sponge/src/main/java/com/griefdefender/claim/GDShovelType.java new file mode 100644 index 0000000..521a1d2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDShovelType.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.griefdefender.api.claim.ShovelType; + +public class GDShovelType implements ShovelType { + + private final String id; + private final String name; + + public GDShovelType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDTown.java b/sponge/src/main/java/com/griefdefender/claim/GDTown.java new file mode 100644 index 0000000..1b5599d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDTown.java @@ -0,0 +1,21 @@ +package com.griefdefender.claim; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.Town; +import com.griefdefender.api.data.TownData; +import org.spongepowered.api.world.World; + +import java.util.UUID; + +public class GDTown extends GDClaim implements Town { + + public GDTown(World world, Vector3i point1, Vector3i point2, ClaimType type, UUID ownerUniqueId, boolean cuboid) { + super(world, point1, point2, type, ownerUniqueId, cuboid); + } + + @Override + public TownData getData() { + return (TownData) this.claimData; + } +} diff --git a/sponge/src/main/java/com/griefdefender/claim/GDTrustType.java b/sponge/src/main/java/com/griefdefender/claim/GDTrustType.java new file mode 100644 index 0000000..3a299b5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/claim/GDTrustType.java @@ -0,0 +1,53 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.claim; + +import com.griefdefender.api.claim.TrustType; + +public class GDTrustType implements TrustType { + + private final String id; + private final String name; + + public GDTrustType(String id, String name) { + this.id = id.toLowerCase(); + this.name = name.toLowerCase(); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String toString() { + return this.id; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/ClaimFlagBase.java b/sponge/src/main/java/com/griefdefender/command/ClaimFlagBase.java new file mode 100644 index 0000000..affaefa --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/ClaimFlagBase.java @@ -0,0 +1,973 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.ResultTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.category.CustomFlagGroupCategory; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDFlagPermissionEvent; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.CustomFlagData; +import com.griefdefender.permission.flag.GDActiveFlagData; +import com.griefdefender.permission.flag.GDCustomFlagDefinition; +import com.griefdefender.permission.ui.ClaimClickData; +import com.griefdefender.permission.ui.FlagData; +import com.griefdefender.permission.ui.MenuType; +import com.griefdefender.permission.ui.UIHelper; +import com.griefdefender.permission.ui.FlagData.FlagContextHolder; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.CauseContextHelper; +import com.griefdefender.util.PaginationUtil; +import com.griefdefender.util.PermissionUtil; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.item.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public abstract class ClaimFlagBase extends BaseCommand { + + private static final Component whiteOpenBracket = TextComponent.of("[", TextColor.AQUA); + private static final Component whiteCloseBracket = TextComponent.of("]", TextColor.AQUA); + protected GDPermissionHolder subject; + protected ClaimSubjectType subjectType; + protected String friendlySubjectName; + private final Cache<UUID, String> lastActivePresetMenuMap = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + private final Cache<UUID, String> lastActiveMenuTypeMap = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + protected ClaimFlagBase(ClaimSubjectType type) { + this.subjectType = type; + } + + public void execute(Player player, String[] args) throws InvalidCommandArgument { + final GDPermissionUser src = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDPermissionHolder commandSubject = subject; + String commandFlag = null; + String target = null; + String value = null; + String contexts = null; + final String arguments = String.join(" ", args); + int index = arguments.indexOf("context["); + if (index != -1) { + contexts = arguments.substring(index, arguments.length()); + } + if (args.length > 0) { + if (args.length < 3) { + throw new InvalidCommandArgument(); + } + commandFlag = args[0]; + target = args[1]; + value = args[2]; + } + final Flag flag = FlagRegistryModule.getInstance().getById(commandFlag).orElse(null); + if (commandFlag != null && flag == null) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_NOT_FOUND, ImmutableMap.of( + "flag", commandFlag))); + return; + } + + if (flag != null && !player.hasPermission(GDPermissions.USER_CLAIM_FLAGS + "." + flag.getName().toLowerCase())) { + TextAdapter.sendComponent(player, MessageCache.getInstance().PERMISSION_FLAG_USE); + return; + } + + if (target != null && target.equalsIgnoreCase("hand")) { + ItemStack stack = NMSUtil.getInstance().getActiveItem(player); + if (stack != null) { + target = GDPermissionManager.getInstance().getPermissionIdentifier(stack); + } + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Set<Context> contextSet = CauseContextHelper.generateContexts(player, claim, contexts); + if (contextSet == null) { + return; + } + + if (claim != null) { + if (flag == null && value == null && player.hasPermission(GDPermissions.COMMAND_LIST_CLAIM_FLAGS)) { + String defaultGroup = ""; + for (Entry<String, CustomFlagGroupCategory> groupEntry : GriefDefenderPlugin.getGlobalConfig().getConfig().customFlags.getGroups().entrySet()) { + final String permission = groupEntry.getValue().isAdminGroup() ? GDPermissions.FLAG_CUSTOM_ADMIN_BASE : GDPermissions.FLAG_CUSTOM_USER_BASE; + if (!player.hasPermission(permission + "." + groupEntry.getKey()) && !src.getInternalPlayerData().canIgnoreClaim(claim)) { + continue; + } + defaultGroup = groupEntry.getKey(); + break; + } + if (!defaultGroup.isEmpty()) { + showCustomFlags(src, claim, defaultGroup); + } else { + TextAdapter.sendComponent(player, MessageCache.getInstance().PERMISSION_FLAG_USE); + } + return; + } else if (flag != null && value != null) { + GDCauseStackManager.getInstance().pushCause(player); + PermissionResult result = CommandHelper.addFlagPermission(player, this.subject, claim, flag, target, PermissionUtil.getInstance().getTristateFromString(value.toUpperCase()), contextSet); + final String flagTarget = target; + if (result.getResultType() == ResultTypes.TARGET_NOT_VALID) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_INVALID_TARGET, + ImmutableMap.of("target", flagTarget, + "flag", flag))); + } else if (result.getResultType() == ResultTypes.NO_PERMISSION) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_FLAG_USE); + } + GDCauseStackManager.getInstance().popCause(); + return; + } + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_FOUND); + } + } + + protected void showCustomFlags(GDPermissionUser src, GDClaim claim, String displayGroup) { + final Player player = src.getOnlinePlayer(); + final String lastPermissionMenuType = this.lastActivePresetMenuMap.getIfPresent(player.getUniqueId()); + if (lastPermissionMenuType != null && !lastPermissionMenuType.equalsIgnoreCase(displayGroup.toLowerCase())) { + PaginationUtil.getInstance().resetActivePage(player.getUniqueId()); + } + + TextComponent.Builder flagHeadBuilder = TextComponent.builder() + .append(" Displaying :", TextColor.AQUA); + final Map<String, CustomFlagGroupCategory> flagGroups = GriefDefenderPlugin.getGlobalConfig().getConfig().customFlags.getGroups(); + List<String> groups = new ArrayList<>(); + for (Map.Entry<String, CustomFlagGroupCategory> flagGroupEntry : flagGroups.entrySet()) { + final CustomFlagGroupCategory flagGroupCat = flagGroupEntry.getValue(); + if (!flagGroupCat.isEnabled()) { + continue; + } + final String groupName = flagGroupEntry.getKey(); + final boolean isAdminGroup = GriefDefenderPlugin.getGlobalConfig().getConfig().customFlags.getGroups().get(groupName).isAdminGroup(); + final String permission = isAdminGroup ? GDPermissions.FLAG_CUSTOM_ADMIN_BASE : GDPermissions.FLAG_CUSTOM_USER_BASE; + if (!player.hasPermission(permission + "." + groupName) && !src.getInternalPlayerData().canIgnoreClaim(claim)) { + continue; + } + + groups.add(groupName); + } + + final CustomFlagGroupCategory flagGroupCat = flagGroups.get(displayGroup); + if (flagGroupCat == null || flagGroupCat.getFlagDefinitions().isEmpty()) { + TextAdapter.sendComponent(player, TextComponent.of("No custom flag definitions were found for group '" + displayGroup + "'.")); + return; + } + + Collections.sort(groups); + for (String group : groups) { + flagHeadBuilder.append(" ").append(displayGroup.equalsIgnoreCase(group) ? TextComponent.builder() + .append(whiteOpenBracket) + .append(group.toUpperCase(), flagGroups.get(group).isAdminGroup() ? TextColor.RED : TextColor.GOLD) + .append(whiteCloseBracket).build() : + TextComponent.builder().append(group.toUpperCase(), TextColor.GRAY) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createCustomFlagConsumer(src, claim, group)))) + .build()); + } + + List<Component> textComponents = new ArrayList<>(); + for (GDCustomFlagDefinition customFlag : flagGroupCat.getFlagDefinitions().values()) { + Component flagText = TextComponent.builder() + .append(getCustomFlagText(customFlag)) + .append(" ") + .append(this.getCustomClickableText(src, claim, customFlag, displayGroup)) + .build(); + textComponents.add(flagText); + } + + + Collections.sort(textComponents, UIHelper.PLAIN_COMPARATOR); + int fillSize = 20 - (textComponents.size() + 2); + for (int i = 0; i < fillSize; i++) { + textComponents.add(TextComponent.of(" ")); + } + + String lastMenu = this.lastActiveMenuTypeMap.getIfPresent(src.getUniqueId()); + MenuType lastActiveMenu = MenuType.CLAIM; + if (lastMenu != null) { + lastActiveMenu = MenuType.valueOf(lastMenu.toUpperCase()); + } + Component footer = null; + if (player.hasPermission(GDPermissions.ADVANCED_FLAGS)) { + footer = TextComponent.builder().append(whiteOpenBracket) + .append(TextComponent.of("PRESET").color(TextColor.GOLD)).append(whiteCloseBracket) + .append(" ") + .append(TextComponent.builder() + .append(TextComponent.of("ADVANCED").color(TextColor.GRAY) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimFlagConsumer(src, claim, lastActiveMenu))))) + .build()) + .build(); + } + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(flagHeadBuilder.build()).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textComponents).footer(footer); + final PaginationList paginationList = paginationBuilder.build(); + Integer activePage = 1; + activePage = PaginationUtil.getInstance().getActivePage(player.getUniqueId()); + if (activePage == null) { + activePage = 1; + } + this.lastActivePresetMenuMap.put(player.getUniqueId(), displayGroup.toLowerCase()); + paginationList.sendTo(player, activePage); + } + + protected void showFlagPermissions(GDPermissionUser src, GDClaim claim, MenuType displayType) { + final Player player = src.getOnlinePlayer(); + boolean isAdmin = false; + if (player.hasPermission(GDPermissions.DELETE_CLAIM_ADMIN)) { + isAdmin = true; + } + + final String lastPermissionMenuType = this.lastActiveMenuTypeMap.getIfPresent(player.getUniqueId()); + if (lastPermissionMenuType != null && !lastPermissionMenuType.equalsIgnoreCase(displayType.name())) { + PaginationUtil.getInstance().resetActivePage(player.getUniqueId()); + } + + final Component showOverrideText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("OVERRIDE", TextColor.RED))); + final Component showDefaultText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("DEFAULT", TextColor.LIGHT_PURPLE))); + final Component showClaimText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("CLAIM", TextColor.GOLD))); + final Component showInheritText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("INHERIT", TextColor.AQUA))); + Component defaultFlagText = TextComponent.empty(); + if (isAdmin) { + defaultFlagText = TextComponent.builder("") + .append(displayType == MenuType.DEFAULT ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("DEFAULT", TextColor.LIGHT_PURPLE) + .append(whiteCloseBracket).build() : TextComponent.of("DEFAULT", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimFlagConsumer(src, claim, MenuType.DEFAULT)))) + .hoverEvent(HoverEvent.showText(showDefaultText)).build(); + } + final Component overrideFlagText = TextComponent.builder("") + .append(displayType == MenuType.OVERRIDE ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("OVERRIDE", TextColor.RED) + .append(whiteCloseBracket).build() : TextComponent.of("OVERRIDE", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimFlagConsumer(src, claim, MenuType.OVERRIDE)))) + .hoverEvent(HoverEvent.showText(showOverrideText)).build(); + final Component claimFlagText = TextComponent.builder("") + .append(displayType == MenuType.CLAIM ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("CLAIM", TextColor.YELLOW) + .append(whiteCloseBracket).build() : TextComponent.of("CLAIM", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimFlagConsumer(src, claim, MenuType.CLAIM)))) + .hoverEvent(HoverEvent.showText(showClaimText)).build(); + final Component inheritFlagText = TextComponent.builder("") + .append(displayType == MenuType.INHERIT ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("INHERIT", TextColor.AQUA) + .append(whiteCloseBracket).build() : TextComponent.of("INHERIT", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimFlagConsumer(src, claim, MenuType.INHERIT)))) + .hoverEvent(HoverEvent.showText(showInheritText)).build(); + Component claimFlagHead = TextComponent.empty(); + if (this.subjectType == ClaimSubjectType.GLOBAL) { + if (isAdmin) { + claimFlagHead = TextComponent.builder("") + .append(" Displaying : ", TextColor.AQUA) + .append(defaultFlagText) + .append(" ") + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } else { + claimFlagHead = TextComponent.builder("") + .append(" Displaying : ", TextColor.AQUA) + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } + } else { + claimFlagHead = TextComponent.builder("") + .append(" " + this.subjectType.getFriendlyName() + " ", TextColor.AQUA) + .append(this.friendlySubjectName, TextColor.YELLOW) + .append(" : ", TextColor.AQUA) + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } + + Set<Context> defaultContexts = new HashSet<>(); + Set<Context> overrideContexts = new HashSet<>(); + if (claim.isAdminClaim()) { + defaultContexts.add(ClaimContexts.ADMIN_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.ADMIN_OVERRIDE_CONTEXT); + } else if (claim.isBasicClaim() || claim.isSubdivision()) { + defaultContexts.add(ClaimContexts.BASIC_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.BASIC_OVERRIDE_CONTEXT); + } else if (claim.isTown()) { + defaultContexts.add(ClaimContexts.TOWN_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.TOWN_OVERRIDE_CONTEXT); + } else { + defaultContexts.add(ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT); + } + defaultContexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + overrideContexts.add(claim.getOverrideClaimContext()); + + Map<String, FlagData> filteredContextMap = new HashMap<>(); + for (Map.Entry<Set<Context>, Map<String, Boolean>> mapEntry : PermissionUtil.getInstance().getTransientPermissions(this.subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + if (contextSet.contains(claim.getDefaultTypeContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } else if (contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT)) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } + } + + final List<Claim> inheritParents = claim.getInheritedParents(); + for (Map.Entry<Set<Context>, Map<String, Boolean>> mapEntry : PermissionUtil.getInstance().getPermanentPermissions(this.subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + if (contextSet.contains(claim.getDefaultTypeContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } else if (contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT)) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } + if (displayType != MenuType.DEFAULT) { + if (contextSet.contains(claim.getContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.CLAIM, mapEntry.getValue()); + } + for (Claim parentClaim : inheritParents) { + GDClaim parent = (GDClaim) parentClaim; + // check parent context + if (contextSet.contains(parent.getContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.INHERIT, mapEntry.getValue()); + } + } + if (contextSet.contains(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT)) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } + if (contextSet.contains(claim.getOverrideClaimContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } else if (contextSet.contains(claim.getOverrideTypeContext())) { + this.addFilteredContexts(filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } + } + } + + final Map<String, Map<Integer, Component>> textMap = new TreeMap<>(); + for (Entry<String, FlagData> mapEntry : filteredContextMap.entrySet()) { + final FlagData flagData = mapEntry.getValue(); + final Flag flag = flagData.flag; + for (FlagContextHolder flagHolder : flagData.flagContextMap.values()) { + if (displayType != MenuType.CLAIM && flagHolder.getType() != displayType) { + continue; + } + + final Set<Context> contexts = flagHolder.getAllContexts(); + Component flagText = TextComponent.builder() + .append(getFlagText(flag, contexts)) + .append(" ") + .append(this.getClickableText(src, claim, flag, flagHolder, contexts, displayType)) + .build(); + final int hashCode = Objects.hash(flag.getPermission(), contexts); + Map<Integer, Component> componentMap = textMap.get(flag.getPermission()); + if (componentMap == null) { + componentMap = new HashMap<>(); + componentMap.put(hashCode, flagText); + textMap.put(flag.getPermission(), componentMap); + } else { + componentMap.put(hashCode, flagText); + } + } + } + + List<Component> textList = new ArrayList<>(); + for (Entry<String, Map<Integer, Component>> mapEntry : textMap.entrySet()) { + textList.addAll(mapEntry.getValue().values()); + } + + Collections.sort(textList, UIHelper.PLAIN_COMPARATOR); + int fillSize = 20 - (textList.size() + 2); + for (int i = 0; i < fillSize; i++) { + textList.add(TextComponent.of(" ")); + } + + String lastActivePresetMenu = this.lastActivePresetMenuMap.getIfPresent(src.getUniqueId()); + if (lastActivePresetMenu == null) { + lastActivePresetMenu = "user"; + } + Component footer = null; + if (player.hasPermission(GDPermissions.ADVANCED_FLAGS)) { + footer = TextComponent.builder().append(TextComponent.builder() + .append(TextComponent.of("PRESET").color(TextColor.GRAY) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createCustomFlagConsumer(src, claim, lastActivePresetMenu))))) + .build()) + .append(" ") + .append(whiteOpenBracket) + .append(TextComponent.of("ADVANCED").color(TextColor.RED)) + .append(whiteCloseBracket) + .build(); + } + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(claimFlagHead).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList).footer(footer); + final PaginationList paginationList = paginationBuilder.build(); + Integer activePage = 1; + activePage = PaginationUtil.getInstance().getActivePage(player.getUniqueId()); + if (activePage == null) { + activePage = 1; + } + this.lastActiveMenuTypeMap.put(player.getUniqueId(), displayType.name().toLowerCase()); + paginationList.sendTo(player, activePage); + } + + private void addFilteredContexts(Map<String, FlagData> filteredContextMap, Set<Context> contexts, MenuType type, Map<String, Boolean> permissions) { + for (Map.Entry<String, Boolean> permissionEntry : permissions.entrySet()) { + final Flag flag = FlagRegistryModule.getInstance().getById(permissionEntry.getKey()).orElse(null); + if (flag == null) { + continue; + } + final FlagData flagData = filteredContextMap.get(permissionEntry.getKey()); + if (flagData != null) { + flagData.addContexts(flag, permissionEntry.getValue(), type, contexts); + } else { + filteredContextMap.put(permissionEntry.getKey(), new FlagData(flag, permissionEntry.getValue(), type, contexts)); + } + } + } + + private Component getCustomFlagText(GDCustomFlagDefinition customFlag) { + TextComponent definitionType = TextComponent.empty(); + TextColor flagColor = TextColor.GREEN; + for (Context context : customFlag.getDefinitionContexts()) { + if (context.getKey().contains("default")) { + definitionType = TextComponent.builder() + .append("\n") + .append(MessageCache.getInstance().LABEL_TYPE.color(TextColor.AQUA)) + .append(" : ", TextColor.WHITE) + .append("DEFAULT", TextColor.LIGHT_PURPLE) + .append(" ") + .append(context.getValue().toUpperCase(), TextColor.GRAY) + .build(); + flagColor = TextColor.LIGHT_PURPLE; + } else if (context.getKey().contains("override")) { + definitionType = TextComponent.builder() + .append("\n") + .append(MessageCache.getInstance().LABEL_TYPE.color(TextColor.AQUA)) + .append(" : ", TextColor.WHITE) + .append("OVERRIDE", TextColor.RED) + .append(" ") + .append(context.getValue().toUpperCase(), TextColor.GRAY) + .build(); + flagColor = TextColor.RED; + } + } + if (definitionType == TextComponent.empty()) { + definitionType = TextComponent.builder() + .append("\n") + .append(MessageCache.getInstance().LABEL_TYPE.color(TextColor.AQUA)) + .append(" : ", TextColor.WHITE) + .append("CLAIM", TextColor.YELLOW) + .build(); + } + final Component baseFlagText = TextComponent.builder() + .append(customFlag.getDisplayName(), flagColor) + .append(" ") + .hoverEvent(HoverEvent.showText(TextComponent.builder() + .append(customFlag.getDescription()) + .append(definitionType) + .build())).build(); + return baseFlagText; + } + + private TextColor getCustomFlagColor(GDCustomFlagDefinition customFlag) { + TextColor flagColor = TextColor.GREEN; + for (Context context : customFlag.getDefinitionContexts()) { + if (context.getKey().contains("default")) { + flagColor = TextColor.LIGHT_PURPLE; + break; + } else if (context.getKey().contains("override")) { + flagColor = TextColor.RED; + break; + } + } + return flagColor; + } + + private Component getFlagText(Flag flag, Set<Context> contexts) { + boolean customContext = UIHelper.containsCustomContext(contexts); + + final Component baseFlagText = TextComponent.builder().color(customContext ? TextColor.YELLOW : TextColor.GREEN).append(flag.getName() + " ") + .hoverEvent(HoverEvent.showText(TextComponent.builder() + .append(flag.getDescription()) + .build())).build(); + return baseFlagText; + } + + private Component getCustomClickableText(GDPermissionUser src, GDClaim claim, GDCustomFlagDefinition customFlag, String flagGroup) { + boolean hasHover = false; + TextComponent.Builder hoverBuilder = TextComponent.builder(); + final Player player = src.getOnlinePlayer(); + boolean hasEditPermission = true; + Component denyReason = claim.allowEdit(player); + if (denyReason != null) { + hoverBuilder.append(denyReason).append("\n"); + hasEditPermission = false; + hasHover = true; + } + + final boolean isAdminGroup = GriefDefenderPlugin.getGlobalConfig().getConfig().customFlags.getGroups().get(flagGroup).isAdminGroup(); + final String permission = isAdminGroup ? GDPermissions.FLAG_CUSTOM_ADMIN_BASE : GDPermissions.FLAG_CUSTOM_USER_BASE; + // check flag perm + if (!player.hasPermission(permission + "." + flagGroup + "." + customFlag.getDisplayName())) { + hoverBuilder.append(MessageCache.getInstance().PERMISSION_FLAG_USE).append("\n"); + hasEditPermission = false; + hasHover = true; + } + + List<GDActiveFlagData> dataResults = new ArrayList<>(); + boolean hasGDContext = false; + Set<Context> definitionContexts = new HashSet<>(customFlag.getDefinitionContexts()); + for (Context context : customFlag.getDefinitionContexts()) { + if (context.getKey().contains("gd_claim")) { + hasGDContext = true; + break; + } + } + if (!hasGDContext) { + definitionContexts.add(claim.getContext()); + } + for (CustomFlagData flagData : customFlag.getFlagData()) { + final Set<Context> filteredContexts = new HashSet<>(); + for (Context context : definitionContexts) { + if (context.getKey().contains("gd_claim")) { + continue; + } + + filteredContexts.add(context); + } + + // Check override + filteredContexts.addAll(flagData.getContexts()); + Set<Context> newContexts = new HashSet<>(filteredContexts); + newContexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + newContexts.add(claim.getOverrideTypeContext()); + newContexts.add(claim.getOverrideClaimContext()); + Tristate result = PermissionUtil.getInstance().getPermissionValueWithRequiredContexts(claim, GriefDefenderPlugin.DEFAULT_HOLDER, flagData.getFlag().getPermission(), newContexts, "gd_claim"); + if (result != Tristate.UNDEFINED) { + dataResults.add(new GDActiveFlagData(flagData, result, GDActiveFlagData.Type.OVERRIDE)); + continue; + } + + // Check claim + newContexts = new HashSet<>(filteredContexts); + newContexts.add(claim.getContext()); + result = PermissionUtil.getInstance().getPermissionValueWithRequiredContexts(claim, GriefDefenderPlugin.DEFAULT_HOLDER, flagData.getFlag().getPermission(), newContexts, "gd_claim"); + if (result != Tristate.UNDEFINED) { + dataResults.add(new GDActiveFlagData(flagData, result, GDActiveFlagData.Type.CLAIM)); + continue; + } + + // Check default + newContexts = new HashSet<>(filteredContexts); + newContexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + newContexts.add(claim.getDefaultTypeContext()); + result = PermissionUtil.getInstance().getPermissionValueWithRequiredContexts(claim, GriefDefenderPlugin.DEFAULT_HOLDER, flagData.getFlag().getPermission(), newContexts, "gd_claim"); + if (result != Tristate.UNDEFINED) { + dataResults.add(new GDActiveFlagData(flagData, result, GDActiveFlagData.Type.DEFAULT)); + continue; + } + dataResults.add(new GDActiveFlagData(flagData, result, GDActiveFlagData.Type.UNDEFINED)); + } + boolean properResult = true; + Tristate lastResult = null; + for (GDActiveFlagData activeFlagData : dataResults) { + final Tristate result = activeFlagData.getValue(); + if (lastResult == null) { + lastResult = result; + } else if (lastResult != result) { + properResult = false; + break; + } + } + + TextComponent.Builder valueBuilder = TextComponent.builder(); + if (!properResult) { + if (hasEditPermission) { + hoverBuilder.append("Active Data : \n"); + for (GDActiveFlagData activeFlagData : dataResults) { + hoverBuilder.append(activeFlagData.getComponent()) + .append("\n"); + } + hasHover = true; + } + valueBuilder.append("partial"); + lastResult = null; + } else { + TextColor valueColor = TextColor.GRAY; + if (lastResult == Tristate.TRUE) { + valueColor = TextColor.GOLD; + } else if (lastResult == Tristate.FALSE) { + valueColor = TextColor.RED; + } + valueBuilder.append(String.valueOf(lastResult).toLowerCase(), valueColor); + } + + if (hasEditPermission) { + if (lastResult == null || lastResult == Tristate.UNDEFINED) { + hoverBuilder.append(MessageCache.getInstance().FLAG_UI_CLICK_ALLOW); + } else if (lastResult == Tristate.TRUE) { + hoverBuilder.append(MessageCache.getInstance().FLAG_UI_CLICK_DENY); + } else { + hoverBuilder.append(MessageCache.getInstance().FLAG_UI_CLICK_REMOVE); + } + + if (!customFlag.getDefinitionContexts().isEmpty()) { + hoverBuilder.append("\nContexts: "); + } + + for (Context context : customFlag.getDefinitionContexts()) { + hoverBuilder.append("\n"); + final String key = context.getKey(); + final String value = context.getValue(); + TextColor keyColor = TextColor.AQUA; + if (key.contains("default")) { + keyColor = TextColor.LIGHT_PURPLE; + } else if (key.contains("override")) { + keyColor = TextColor.RED; + } else if (key.contains("server")) { + keyColor = TextColor.GRAY; + } + hoverBuilder.append(key, keyColor) + .append("=", TextColor.WHITE) + .append(value.replace("minecraft:", ""), TextColor.GRAY); + } + hasHover = true; + } + + if (hasHover) { + valueBuilder.hoverEvent(HoverEvent.showText(hoverBuilder.build())); + } + TextComponent.Builder textBuilder = null; + if (hasEditPermission) { + textBuilder = TextComponent.builder() + .append(valueBuilder + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createCustomFlagConsumer(src, claim, customFlag, lastResult, flagGroup)))) + .build()); + } else { + textBuilder = TextComponent.builder() + .append(valueBuilder + .build()); + } + + return textBuilder.build(); + } + + private Component getClickableText(GDPermissionUser src, GDClaim claim, Flag flag, FlagContextHolder flagHolder, Set<Context> contexts, MenuType displayType) { + Component hoverEventText = TextComponent.empty(); + final MenuType flagType = flagHolder.getType(); + final Player player = src.getOnlinePlayer(); + boolean hasEditPermission = true; + if (displayType == MenuType.DEFAULT) { + if (!src.getInternalPlayerData().canManageFlagDefaults) { + hoverEventText = MessageCache.getInstance().PERMISSION_FLAG_DEFAULTS; + hasEditPermission = false; + } + } else if (flagType == MenuType.OVERRIDE) { + if (!src.getInternalPlayerData().canManageFlagOverrides) { + hoverEventText = MessageCache.getInstance().PERMISSION_FLAG_OVERRIDES; + hasEditPermission = false; + } + } else if (flagType == MenuType.INHERIT) { + UUID parentUniqueId = null; + for (Context context : contexts) { + if (context.getKey().equals("gd_claim")) { + try { + parentUniqueId = UUID.fromString(context.getValue()); + } catch (IllegalArgumentException e) { + // ignore + } + } + } + // should never happen but just in case + if (parentUniqueId == null) { + hoverEventText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_UI_INHERIT_PARENT, + ImmutableMap.of("name", "unknown")); + hasEditPermission = false; + } else { + final GDClaim parentClaim = (GDClaim) GriefDefenderPlugin.getInstance().dataStore.getClaim(claim.getWorldUniqueId(), parentUniqueId); + hoverEventText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_UI_INHERIT_PARENT, + ImmutableMap.of("name", parentClaim.getFriendlyNameType())); + hasEditPermission = false; + } + } else { + Component denyReason = claim.allowEdit(player); + if (denyReason != null) { + hoverEventText = denyReason; + hasEditPermission = false; + } else { + // check flag perm + if (!player.hasPermission(GDPermissions.USER_CLAIM_FLAGS + "." + flag.getName().toLowerCase())) { + hoverEventText = MessageCache.getInstance().PERMISSION_FLAG_USE; + hasEditPermission = false; + } + } + } + + Set<Context> sortedContexts = new TreeSet<>(new Comparator<Context>() { + @Override + public int compare(Context o1, Context o2) { + return o1.getKey().compareTo(o2.getKey()); + } + }); + sortedContexts.addAll(contexts); + + final boolean customContexts = UIHelper.containsCustomContext(contexts); + Component flagContexts = UIHelper.getFriendlyContextString(claim, contexts); + + Component hoverText = TextComponent.builder() + .append(hoverEventText) + .append(hoverEventText == TextComponent.empty() ? "" : "\n") + .append(UIHelper.getPermissionMenuTypeHoverText(flagHolder, displayType)) + .append("\n") + .append(flagContexts) + .build(); + + Tristate newValue = Tristate.UNDEFINED; + Boolean value = flagHolder.getValue(); + + if (customContexts) { + if (value) { + newValue = Tristate.FALSE; + } else { + newValue = Tristate.TRUE; + } + } else { + if (displayType == MenuType.DEFAULT || (displayType == MenuType.CLAIM && flagHolder.getType() == MenuType.DEFAULT)) { + newValue = Tristate.fromBoolean(!value); + } else { + // Always fall back to transient default + newValue = Tristate.UNDEFINED; + } + } + + TextComponent.Builder valueBuilder = TextComponent.builder() + .append(String.valueOf(value), flagHolder.getColor()) + .hoverEvent(HoverEvent.showText(hoverText)); + TextComponent.Builder textBuilder = null; + if (hasEditPermission) { + textBuilder = TextComponent.builder() + .append(valueBuilder + .hoverEvent(HoverEvent.showText(hoverText)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createFlagConsumer(src, claim, flag, flagHolder, newValue, contexts, displayType)))) + .build()); + } else { + textBuilder = TextComponent.builder() + .append(valueBuilder + .hoverEvent(HoverEvent.showText(hoverText)) + .build()); + } + + // check source/target + Component source = null; + Component target = null; + final Component whiteOpenBracket = TextComponent.of("[", TextColor.WHITE); + final Component whiteCloseBracket = TextComponent.of("]", TextColor.WHITE); + for (Context context : contexts) { + if (context.getKey().equals(ContextKeys.SOURCE)) { + source = TextComponent.builder() + .append(whiteOpenBracket) + .append("s", TextColor.GREEN) + .append("=", TextColor.WHITE) + .append(context.getValue().replace("minecraft:", ""), TextColor.GOLD) + .append(whiteCloseBracket) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().LABEL_SOURCE)) + .build(); + textBuilder.append(" ").append(source); + } else if (context.getKey().equals(ContextKeys.TARGET)) { + target = TextComponent.builder() + .append(whiteOpenBracket) + .append("t", TextColor.GREEN) + .append("=", TextColor.WHITE) + .append(context.getValue().replace("minecraft:", ""), TextColor.GOLD) + .append(whiteCloseBracket) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().LABEL_TARGET)) + .build(); + textBuilder.append(" ").append(target); + } + } + + if (customContexts) { + textBuilder.append(" ") + .append("[", TextColor.WHITE) + .append(TextComponent.builder() + .append("x", TextColor.RED) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().FLAG_UI_CLICK_REMOVE)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createFlagConsumer(src, claim, flag, flagHolder, Tristate.UNDEFINED, contexts, displayType)))) + .build()) + .append("]", TextColor.WHITE); + } + + return textBuilder.build(); + } + + private Consumer<CommandSource> createCustomFlagConsumer(GDPermissionUser src, GDClaim claim, GDCustomFlagDefinition customFlag, Tristate currentValue, String displayType) { + final Player player = src.getOnlinePlayer(); + return consumer -> { + GDCauseStackManager.getInstance().pushCause(player); + boolean hasGDContext = false; + Set<Context> definitionContexts = new HashSet<>(customFlag.getDefinitionContexts()); + for (Context context : customFlag.getDefinitionContexts()) { + if (context.getKey().contains("gd_claim")) { + hasGDContext = true; + break; + } + } + if (!hasGDContext) { + definitionContexts.add(claim.getContext()); + } + for (CustomFlagData flagData : customFlag.getFlagData()) { + final Set<Context> newContexts = new HashSet<>(definitionContexts); + newContexts.addAll(flagData.getContexts()); + Tristate newValue = Tristate.UNDEFINED; + if (currentValue == null || currentValue == Tristate.UNDEFINED) { + newValue = Tristate.TRUE; + } else if (currentValue == Tristate.TRUE) { + newValue = Tristate.FALSE; + } else { + newValue = Tristate.UNDEFINED; + } + + PermissionResult result = null; + final Flag flag = flagData.getFlag(); + GDFlagPermissionEvent.Set event = new GDFlagPermissionEvent.Set(this.subject, flagData.getFlag(), newValue, newContexts); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return; + } + + result = GriefDefenderPlugin.getInstance().getPermissionProvider().setPermissionValue(GriefDefenderPlugin.DEFAULT_HOLDER, flag, newValue, newContexts); + } + GDCauseStackManager.getInstance().popCause(); + showCustomFlags(src, claim, displayType); + }; + } + + private Consumer<CommandSource> createFlagConsumer(GDPermissionUser src, GDClaim claim, Flag flag, FlagContextHolder flagHolder, Tristate newValue, Set<Context> contexts, MenuType displayType) { + final Player player = src.getOnlinePlayer(); + return consumer -> { + Set<Context> newContexts = new HashSet<>(); + GDCauseStackManager.getInstance().pushCause(player); + final boolean isCustom = UIHelper.containsCustomContext(contexts); + if (!isCustom && displayType == MenuType.CLAIM) { + newContexts.add(claim.getContext()); + } else { + newContexts.addAll(contexts); + } + + // Remove server context + final Iterator<Context> iterator = newContexts.iterator(); + while (iterator.hasNext()) { + final Context context = iterator.next(); + if (context.getKey().equals("server")) { + iterator.remove(); + } + } + + GDFlagPermissionEvent.Set event = new GDFlagPermissionEvent.Set(this.subject, flag, newValue, newContexts); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + return; + } + + PermissionResult result = PermissionUtil.getInstance().setPermissionValue(this.subject, flag, newValue, newContexts); + if (result.successful()) { + showFlagPermissions(src, claim, displayType); + } + }; + } + + private Consumer<CommandSource> createCustomFlagConsumer(GDPermissionUser src, GDClaim claim, String flagGroup) { + return consumer -> { + showCustomFlags(src, claim, flagGroup); + }; + } + + private Consumer<CommandSource> createClaimFlagConsumer(GDPermissionUser src, GDClaim claim, MenuType flagType) { + return consumer -> { + showFlagPermissions(src, claim, flagType); + }; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/ClaimOptionBase.java b/sponge/src/main/java/com/griefdefender/command/ClaimOptionBase.java new file mode 100644 index 0000000..581389d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/ClaimOptionBase.java @@ -0,0 +1,913 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.GameModeTypes; +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.api.permission.option.type.WeatherTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.option.GDOption; +import com.griefdefender.permission.ui.ClaimClickData; +import com.griefdefender.permission.ui.FlagData; +import com.griefdefender.permission.ui.MenuType; +import com.griefdefender.permission.ui.OptionData; +import com.griefdefender.permission.ui.OptionData.OptionContextHolder; +import com.griefdefender.permission.ui.UIHelper; +import com.griefdefender.permission.ui.FlagData.FlagContextHolder; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.registry.OptionRegistryModule; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.CauseContextHelper; +import com.griefdefender.util.PaginationUtil; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@SuppressWarnings({ "unchecked", "rawtypes" }) +public abstract class ClaimOptionBase extends BaseCommand { + + protected GDPermissionHolder subject; + protected ClaimSubjectType subjectType; + protected String friendlySubjectName; + private final Cache<UUID, MenuType> lastActiveMenuTypeMap = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + protected ClaimOptionBase(ClaimSubjectType type) { + this.subjectType = type; + } + + public void execute(Player player, String[] args) throws InvalidCommandArgument { + final GDPermissionUser src = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDPermissionHolder commandSubject = subject; + String commandOption = null; + String value = null; + String contexts = null; + final String arguments = String.join(" ", args); + int index = arguments.indexOf("context["); + if (index != -1) { + contexts = arguments.substring(index, arguments.length()); + } + if (args.length > 0) { + if (args.length < 2) { + throw new InvalidCommandArgument(); + } + commandOption = args[0]; + value = args[1]; + } + + Option option = null; + if (commandOption != null) { + option = GriefDefender.getRegistry().getType(Option.class, commandOption).orElse(null); + if (commandOption != null && option == null) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_FOUND, ImmutableMap.of( + "option", commandOption))); + return; + } + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (claim.isWilderness()) { + if(!playerData.canManageWilderness && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GLOBAL_OPTION); + return; + } + } else if (!claim.isTown() && !playerData.canManageAdminClaims && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GLOBAL_OPTION); + return; + } + if (option != null) { + if (option.isGlobal()) { + if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getPermission().toLowerCase())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GLOBAL_OPTION); + return; + } + } else if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getPermission().toLowerCase())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_PLAYER_OPTION); + return; + } + } + + if (!playerData.canManageAdminClaims && !playerData.canIgnoreClaim(claim)) { + final Component denyMessage = claim.allowEdit(player); + if (denyMessage != null) { + GriefDefenderPlugin.sendMessage(player, denyMessage); + return; + } + } + + final Set<Context> contextSet = CauseContextHelper.generateContexts(player, claim, contexts); + if (contextSet == null) { + return; + } + + if (claim != null) { + if (commandOption == null && value == null && player.hasPermission(GDPermissions.COMMAND_LIST_CLAIM_OPTIONS)) { + showOptionPermissions(src, (GDClaim) claim, MenuType.CLAIM); + return; + } else if (option != null && value != null) { + if (!((GDOption) option).validateStringValue(value, false)) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_INVALID_VALUE, + ImmutableMap.of( + "value", value, + "option", option.getName(), + "type", option.getAllowedType().getSimpleName()))); + return; + } + + MenuType type = MenuType.DEFAULT; + for (Context context : contextSet) { + if (context.getKey().equals(ContextKeys.CLAIM)) { + type = MenuType.CLAIM; + break; + } + if (context.getKey().equals(ContextKeys.CLAIM_OVERRIDE)) { + type = MenuType.OVERRIDE; + break; + } + } + if (!option.isGlobal()) { + contextSet.add(claim.getContext()); + if (contextSet.isEmpty() ) { + type = MenuType.CLAIM; + } + } + GDCauseStackManager.getInstance().pushCause(player); + PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), value, contextSet); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_SET_TARGET, + ImmutableMap.of( + "type", type.name().toUpperCase(), + "option", option.getName(), + "contexts", UIHelper.getFriendlyContextString(claim, contextSet), + "value", TextComponent.of(value).color(TextColor.LIGHT_PURPLE), + "target", subject.getFriendlyName()))); + GDCauseStackManager.getInstance().popCause(); + return; + } + } + } + + protected void showOptionPermissions(GDPermissionUser src, GDClaim claim, MenuType displayType) { + boolean isAdmin = false; + final Player player = src.getOnlinePlayer(); + final GDPlayerData playerData = src.getInternalPlayerData(); + final boolean isTaxEnabled = GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()).getConfig().claim.bankTaxSystem; + if (player.hasPermission(GDPermissions.DELETE_CLAIM_ADMIN)) { + isAdmin = true; + } + + final MenuType lastFlagType = this.lastActiveMenuTypeMap.getIfPresent(player.getUniqueId()); + if (lastFlagType != null && lastFlagType != displayType) { + PaginationUtil.getInstance().resetActivePage(player.getUniqueId()); + } + final Component whiteOpenBracket = TextComponent.of("[", TextColor.AQUA); + final Component whiteCloseBracket = TextComponent.of("]", TextColor.AQUA); + final Component showOverrideText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("OVERRIDE", TextColor.RED))); + final Component showDefaultText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("DEFAULT", TextColor.LIGHT_PURPLE))); + final Component showClaimText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("CLAIM", TextColor.GOLD))); + final Component showInheritText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("INHERIT", TextColor.AQUA))); + Component defaultFlagText = TextComponent.empty(); + if (isAdmin) { + defaultFlagText = TextComponent.builder("") + .append(displayType == MenuType.DEFAULT ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("DEFAULT", TextColor.LIGHT_PURPLE) + .append(whiteCloseBracket).build() : TextComponent.of("DEFAULT", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.DEFAULT)))) + .hoverEvent(HoverEvent.showText(showDefaultText)).build(); + } + final Component overrideFlagText = TextComponent.builder("") + .append(displayType == MenuType.OVERRIDE ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("OVERRIDE", TextColor.RED) + .append(whiteCloseBracket).build() : TextComponent.of("OVERRIDE", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.OVERRIDE)))) + .hoverEvent(HoverEvent.showText(showOverrideText)).build(); + final Component claimFlagText = TextComponent.builder("") + .append(displayType == MenuType.CLAIM ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("CLAIM", TextColor.YELLOW) + .append(whiteCloseBracket).build() : TextComponent.of("CLAIM", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.CLAIM)))) + .hoverEvent(HoverEvent.showText(showClaimText)).build(); + final Component inheritFlagText = TextComponent.builder("") + .append(displayType == MenuType.INHERIT ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("INHERIT", TextColor.AQUA) + .append(whiteCloseBracket).build() : TextComponent.of("INHERIT", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimOptionConsumer(src, claim, MenuType.INHERIT)))) + .hoverEvent(HoverEvent.showText(showInheritText)).build(); + Component claimOptionHead = TextComponent.empty(); + if (this.subjectType == ClaimSubjectType.GLOBAL) { + if (isAdmin) { + claimOptionHead = TextComponent.builder("") + .append(" Displaying : ", TextColor.AQUA) + .append(defaultFlagText) + .append(" ") + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } else { + claimOptionHead = TextComponent.builder("") + .append(" Displaying : ", TextColor.AQUA) + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } + } else { + claimOptionHead = TextComponent.builder("") + .append(" " + this.subjectType.getFriendlyName() + " ", TextColor.AQUA) + .append(this.friendlySubjectName, TextColor.YELLOW) + .append(" : ", TextColor.AQUA) + .append(claimFlagText) + .append(" ") + .append(inheritFlagText) + .append(" ") + .append(overrideFlagText).build(); + } + + Set<Context> defaultContexts = new HashSet<>(); + Set<Context> overrideContexts = new HashSet<>(); + if (claim.isAdminClaim()) { + defaultContexts.add(ClaimContexts.ADMIN_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.ADMIN_OVERRIDE_CONTEXT); + } else if (claim.isBasicClaim() || claim.isSubdivision()) { + defaultContexts.add(ClaimContexts.BASIC_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.BASIC_OVERRIDE_CONTEXT); + } else if (claim.isTown()) { + defaultContexts.add(ClaimContexts.TOWN_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.TOWN_OVERRIDE_CONTEXT); + } else { + defaultContexts.add(ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT); + } + defaultContexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + overrideContexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + overrideContexts.add(claim.getOverrideClaimContext()); + + Map<String, OptionData> filteredContextMap = new HashMap<>(); + for (Map.Entry<Set<Context>, Map<String, String>> mapEntry : PermissionUtil.getInstance().getTransientOptions(this.subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + if (contextSet.contains(claim.getDefaultTypeContext()) || contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT)) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } + } + + if (displayType == MenuType.DEFAULT) { + final Set<Context> contexts = new HashSet<>(); + contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + for (Option option : OptionRegistryModule.getInstance().getAll()) { + boolean found = false; + for (Entry<String, OptionData> optionEntry : filteredContextMap.entrySet()) { + if (optionEntry.getValue().option == option) { + found = true; + break; + } + } + if (!found) { + filteredContextMap.put(option.getPermission(), new OptionData(option, "undefined", displayType, contexts)); + } + } + } + + if (displayType == MenuType.CLAIM) { + final Set<Context> contexts = new HashSet<>(); + contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + filteredContextMap.put(Options.PVP.getPermission(), new OptionData(Options.PVP, "undefined", MenuType.DEFAULT, contexts)); + } + for (Map.Entry<Set<Context>, Map<String, String>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + if (contextSet.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT)) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } + if (contextSet.contains(claim.getDefaultTypeContext())) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.DEFAULT, mapEntry.getValue()); + } + if (displayType != MenuType.DEFAULT) { + if (claim.isTown() || isAdmin) { + if (contextSet.contains(claim.getContext())) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.CLAIM, mapEntry.getValue()); + } + } + if (contextSet.contains(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT)) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } + if (contextSet.contains(claim.getOverrideClaimContext())) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } else if (contextSet.contains(claim.getOverrideTypeContext())) { + this.addFilteredContexts(src, filteredContextMap, contextSet, MenuType.OVERRIDE, mapEntry.getValue()); + } + } + } + + Map<Set<Context>, ClaimClickData> inheritPermissionMap = Maps.newHashMap(); + + final List<Claim> inheritParents = claim.getInheritedParents(); + Collections.reverse(inheritParents); + for (Claim current : inheritParents) { + GDClaim currentClaim = (GDClaim) current; + for (Map.Entry<Set<Context>, Map<String, String>> mapEntry : PermissionUtil.getInstance().getPermanentOptions(this.subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + if (contextSet.contains(currentClaim.getContext())) { + inheritPermissionMap.put(mapEntry.getKey(), new ClaimClickData(currentClaim, mapEntry.getValue())); + } + } + } + + final Map<String, Map<Integer, Component>> textMap = new TreeMap<>(); + for (Entry<String, OptionData> mapEntry : filteredContextMap.entrySet()) { + final OptionData optionData = mapEntry.getValue(); + final Option option = optionData.option; + if (option.getName().contains("tax") && !GriefDefenderPlugin.getGlobalConfig().getConfig().claim.bankTaxSystem) { + continue; + } + for (OptionContextHolder optionHolder : optionData.optionContextMap.values()) { + if (displayType != MenuType.CLAIM && optionHolder.getType() != displayType) { + continue; + } + + final Set<Context> contexts = optionHolder.getAllContexts(); + Component optionText = getClickableOptionComponent(src, claim, option, optionHolder, contexts, displayType); + final int hashCode = Objects.hash(option.getPermission(), contexts); + Map<Integer, Component> componentMap = textMap.get(option.getPermission()); + if (componentMap == null) { + componentMap = new HashMap<>(); + componentMap.put(hashCode, optionText); + textMap.put(option.getPermission(), componentMap); + } else { + componentMap.put(hashCode, optionText); + } + } + } + + List<Component> textList = new ArrayList<>(); + for (Entry<String, Map<Integer, Component>> mapEntry : textMap.entrySet()) { + textList.addAll(mapEntry.getValue().values()); + } + + Collections.sort(textList, UIHelper.PLAIN_COMPARATOR); + int fillSize = 20 - (textList.size() + 2); + for (int i = 0; i < fillSize; i++) { + textList.add(TextComponent.of(" ")); + } + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(claimOptionHead).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList); + final PaginationList paginationList = paginationBuilder.build(); + Integer activePage = 1; + activePage = PaginationUtil.getInstance().getActivePage(player.getUniqueId()); + if (activePage == null) { + activePage = 1; + } + this.lastActiveMenuTypeMap.put(player.getUniqueId(), displayType); + paginationList.sendTo(player, activePage); + } + + private void addFilteredContexts(GDPermissionUser src, Map<String, OptionData> filteredContextMap, Set<Context> contexts, MenuType type, Map<String, String> permissions) { + final Player player = src.getOnlinePlayer(); + final GDPlayerData playerData = src.getInternalPlayerData(); + for (Map.Entry<String, String> permissionEntry : permissions.entrySet()) { + final Option option = OptionRegistryModule.getInstance().getById(permissionEntry.getKey()).orElse(null); + if (option == null) { + continue; + } + if (option.getName().contains("tax") && !GriefDefenderPlugin.getGlobalConfig().getConfig().claim.bankTaxSystem) { + continue; + } + + if (option.isGlobal()) { + if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getName().toLowerCase())) { + continue; + } + } else if (((GDOption) option).isAdmin()) { + if (!player.hasPermission(GDPermissions.MANAGE_ADMIN_OPTIONS +"." + option.getName().toLowerCase())) { + continue; + } + } else { + if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getName().toLowerCase())) { + continue; + } + } + final OptionData optionData = filteredContextMap.get(permissionEntry.getKey()); + if (optionData != null) { + optionData.addContexts(option, permissionEntry.getValue(), type, contexts); + } else { + filteredContextMap.put(permissionEntry.getKey(), new OptionData(option, permissionEntry.getValue(), type, contexts)); + } + } + } + + private Component getClickableOptionComponent(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set<Context> contexts, MenuType displayType) { + final Player player = src.getOnlinePlayer(); + final GDPlayerData playerData = src.getInternalPlayerData(); + boolean hasEditPermission = true; + Component hoverEventText = TextComponent.empty(); + final MenuType flagType = optionHolder.getType(); + if (flagType == MenuType.DEFAULT) { + if (!playerData.canManageGlobalOptions) { + hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_DEFAULTS; + hasEditPermission = false; + } + } else if (flagType == MenuType.OVERRIDE) { + if (!playerData.canManageOverrideOptions) { + hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_OVERRIDES; + hasEditPermission = false; + } + } else if (flagType == MenuType.INHERIT) { + hoverEventText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_UI_INHERIT_PARENT, + ImmutableMap.of("name", claim.getFriendlyNameType())); + hasEditPermission = false; + } + if (displayType == MenuType.CLAIM) { + Component denyReason = claim.allowEdit(player); + if (denyReason != null) { + hoverEventText = denyReason; + hasEditPermission = false; + } else { + if (option.isGlobal()) { + if (!player.hasPermission(GDPermissions.MANAGE_GLOBAL_OPTIONS +"." + option.getName().toLowerCase())) { + hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; + hasEditPermission = false; + } + } else if (((GDOption) option).isAdmin()) { + if (!player.hasPermission(GDPermissions.MANAGE_ADMIN_OPTIONS +"." + option.getName().toLowerCase())) { + hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; + hasEditPermission = false; + } + } + else { + if (!player.hasPermission(GDPermissions.USER_CLAIM_OPTIONS +"." + option.getName().toLowerCase())) { + hoverEventText = MessageCache.getInstance().PERMISSION_OPTION_USE; + hasEditPermission = false; + } + } + } + } + + final boolean customContexts = UIHelper.containsCustomContext(contexts); + Component optionContexts = UIHelper.getFriendlyContextString(claim, contexts); + String currentValue = optionHolder.getValue(); + TextColor color = optionHolder.getColor(); + boolean isNumber = false; + if (option.getAllowedType().isAssignableFrom(Integer.class) || option.getAllowedType().isAssignableFrom(Double.class)) { + isNumber = true; + } + + TextComponent.Builder builder = null; + if (isNumber && hasEditPermission) { + builder = TextComponent.builder() + .append(getOptionText(option, contexts)) + .append(" ") + .append(TextComponent.builder() + .append(TextComponent.of("< ").decoration(TextDecoration.BOLD, true)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, true))))) + .append(currentValue.toLowerCase(), color); + } else { + builder = TextComponent.builder() + .append(getOptionText(option, contexts)) + .append(" ") + .append(TextComponent.builder() + .append(currentValue.toLowerCase(), color) + .hoverEvent(HoverEvent.showText(hoverEventText))); + } + if (hasEditPermission) { + if (!option.getAllowedType().isAssignableFrom(Integer.class) && !option.getAllowedType().isAssignableFrom(Double.class)) { + builder.clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, false)))); + } else { + builder.append(TextComponent.builder() + .append(TextComponent.of(" >").decoration(TextDecoration.BOLD, true)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(newOptionValueConsumer(src, claim, option, optionHolder, contexts, displayType, false))))); + } + + if (option.getAllowedType().isAssignableFrom(String.class)) { + builder.clickEvent(createClickEvent(player, option)); + } + } + + if (displayType == MenuType.DEFAULT) { + builder.hoverEvent(HoverEvent.showText(TextComponent.builder().append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_SET, + ImmutableMap.of( + "option", TextComponent.of(option.getName().toLowerCase()).color(TextColor.GREEN), + "value", TextComponent.of(currentValue).color(TextColor.GOLD)))).build())); + } + + return builder.build(); + } + + private boolean containsDefaultContext(Set<Context> contexts) { + for (Context context : contexts) { + if (context.getKey().contains("gd_claim_default")) { + return true; + } + } + return false; + } + + private ClickEvent createClickEvent(Player src, Option option) { + return ClickEvent.suggestCommand("/gd option " + option.getName() + " "); + } + + private Consumer<CommandSource> newOptionValueConsumer(GDPermissionUser src, GDClaim claim, Option option, OptionContextHolder optionHolder, Set<Context> contexts, MenuType displayType, boolean leftArrow) { + final String currentValue = optionHolder.getValue(); + return consumer -> { + String newValue = ""; + if (option.getAllowedType().isAssignableFrom(Tristate.class)) { + Tristate value = getMenuTypeValue(TypeToken.of(Tristate.class), currentValue); + if (value == Tristate.TRUE) { + newValue = "false"; + } else if (value == Tristate.FALSE) { + newValue = "undefined"; + } else { + newValue = "true"; + } + } + if (option.getAllowedType().isAssignableFrom(Boolean.class)) { + Boolean value = getMenuTypeValue(TypeToken.of(Boolean.class), currentValue); + Tristate result = Tristate.UNDEFINED; + if (displayType == MenuType.DEFAULT || (displayType == MenuType.CLAIM && optionHolder.getType() == MenuType.DEFAULT)) { + result = Tristate.fromBoolean(!value); + } else { + // Always fall back to transient default + result = Tristate.UNDEFINED; + } + newValue = result.toString().toLowerCase(); + } + if (option.getAllowedType().isAssignableFrom(GameModeType.class)) { + GameModeType value = getMenuTypeValue(TypeToken.of(GameModeType.class), currentValue); + if (value == null || value == GameModeTypes.UNDEFINED) { + newValue = "adventure"; + } else if (value == GameModeTypes.ADVENTURE) { + newValue = "creative"; + } else if (value == GameModeTypes.CREATIVE) { + newValue = "survival"; + } else if (value == GameModeTypes.SURVIVAL) { + newValue = "spectator"; + } else { + newValue = "undefined"; + } + } + if (option.getAllowedType().isAssignableFrom(CreateModeType.class)) { + CreateModeType value = getMenuTypeValue(TypeToken.of(CreateModeType.class), currentValue); + if (value == null || value == CreateModeTypes.UNDEFINED) { + newValue = "area"; + } else if (value == CreateModeTypes.AREA) { + newValue = "volume"; + } else { + newValue = "undefined"; + } + } + if (option.getAllowedType().isAssignableFrom(WeatherType.class)) { + WeatherType value = getMenuTypeValue(TypeToken.of(WeatherType.class), currentValue); + if (value == null || value == WeatherTypes.UNDEFINED) { + newValue = "clear"; + } else if (value == WeatherTypes.CLEAR) { + newValue = "rain"; + } else { + newValue = "undefined"; + } + } + if (option.getAllowedType().isAssignableFrom(Integer.class)) { + Integer value = getMenuTypeValue(TypeToken.of(Integer.class), currentValue); + if (leftArrow) { + if (value == null || value < 1) { + TextAdapter.sendComponent(src.getOnlinePlayer(), TextComponent.of("This value is NOT defined and cannot go any lower.")); + } else { + if ((option == Options.MIN_LEVEL || option == Options.MAX_LEVEL || option == Options.MIN_SIZE_Y || option == Options.MAX_SIZE_Y) && value == 1) { + value = null; + } else { + value -= 1; + if (value <= 0) { + if (option == Options.MAX_LEVEL) { + value = 255; + } + } + } + } + } else { + if (value == null) { + value = 1; + } else { + if ((option == Options.MIN_SIZE_Y || option == Options.MAX_SIZE_Y) && value == 256) { + value = null; + } else if ((option == Options.MIN_LEVEL || option == Options.MAX_LEVEL) && value == 255) { + value = null; + } else { + value += 1; + } + } + } + newValue = value == null ? "undefined" :String.valueOf(value); + } + if (option.getAllowedType().isAssignableFrom(Double.class)) { + Double value = getMenuTypeValue(TypeToken.of(Double.class), currentValue); + if (leftArrow) { + if (value == null || value < 1) { + TextAdapter.sendComponent(src.getOnlinePlayer(), TextComponent.of("This value is NOT defined and cannot go any lower.")); + } else { + value -= 1; + if (option == Options.ABANDON_RETURN_RATIO && value <= 0) { + value = null; + } else { + if (value < 0) { + value = 0.0; + } + } + } + } else { + if (value == null) { + value = 1.0; + } else { + value += 1; + } + } + newValue = value == null ? "undefined" :String.valueOf(value); + } + + Set<Context> newContexts = new HashSet<>(); + final boolean isCustom = UIHelper.containsCustomContext(contexts); + if (!isCustom && displayType == MenuType.CLAIM) { + newContexts.add(claim.getContext()); + } else { + newContexts.addAll(contexts); + } + + // Remove server context + final Iterator<Context> iterator = newContexts.iterator(); + while (iterator.hasNext()) { + final Context context = iterator.next(); + if (context.getKey().equals("server")) { + iterator.remove(); + } + } + + PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), newValue, newContexts); + showOptionPermissions(src, claim, displayType); + }; + } + + private Consumer<CommandSource> adjustNumberConsumer(GDPermissionUser src, GDClaim claim, Option option, String currentValue, MenuType menuType, Set<Context> contexts) { + return consumer -> { + String newValue = ""; + final Set<Context> filteredContexts = applySelectedTypeContext(claim, menuType, contexts); + if (option.getAllowedType().isAssignableFrom(Boolean.class)) { + Boolean value = getMenuTypeValue(TypeToken.of(Boolean.class), currentValue); + if (value == null) { + newValue = "true"; + } else if (value) { + newValue = "false"; + } else { + newValue = "undefined"; + } + PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), newValue, filteredContexts); + } + if (option.getAllowedType().isAssignableFrom(Integer.class)) { + newValue = getMenuTypeValue(TypeToken.of(String.class), currentValue); + if (newValue == null) { + newValue = "undefined"; + } + PermissionUtil.getInstance().setOptionValue(this.subject, option.getPermission(), newValue, filteredContexts); + } + showOptionPermissions(src, claim, menuType); + }; + } + + private Set<Context> applySelectedTypeContext(Claim claim, MenuType menuType, Set<Context> contexts) { + Set<Context> filteredContexts = new HashSet<>(contexts); + for (Context context : contexts) { + if (context.getKey().contains("gd_claim")) { + filteredContexts.remove(context); + } + } + if (menuType == MenuType.DEFAULT) { + filteredContexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + } + if (menuType == MenuType.CLAIM) { + filteredContexts.add(claim.getContext()); + } + if (menuType == MenuType.OVERRIDE) { + filteredContexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + } + return filteredContexts; + } + + private boolean contextsMatch(Set<Context> contexts, Set<Context> newContexts) { + // first filter out gd claim contexts + final Set<Context> filteredContexts = new HashSet<>(); + final Set<Context> filteredNewContexts = new HashSet<>(); + for (Context context : contexts) { + if (context.getKey().contains("gd_claim")) { + continue; + } + filteredContexts.add(context); + } + for (Context context : newContexts) { + if (context.getKey().contains("gd_claim")) { + continue; + } + filteredNewContexts.add(context); + } + return Objects.hash(filteredContexts) == Objects.hash(filteredNewContexts); + } + + private static Set<Context> createClaimContextSet(GDClaim claim, Set<Context> contexts) { + Set<Context> claimContexts = new HashSet<>(); + claimContexts.add(claim.getContext()); + for (Context context : contexts) { + if (context.getKey().contains("world") || context.getKey().contains("gd_claim")) { + continue; + } + claimContexts.add(context); + } + return claimContexts; + } + + private static Component getOptionText(Option option, Set<Context> contexts) { + boolean customContext = UIHelper.containsCustomContext(contexts); + + final Component optionText = TextComponent.builder().color(customContext ? TextColor.YELLOW : TextColor.GREEN).append(option.getName() + " ") + .hoverEvent(HoverEvent.showText(TextComponent.builder() + .append(option.getDescription()) + .build())).build(); + return optionText; + } + + private static <T> T getMenuTypeValue(TypeToken<T> type, String value) { + if (type.getRawType().isAssignableFrom(Double.class)) { + Double newValue = null; + try { + newValue = Double.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + return (T) newValue; + } + if (type.getRawType().isAssignableFrom(Integer.class)) { + Integer newValue = null; + try { + newValue = Integer.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + return (T) newValue; + } + if (type.getRawType().isAssignableFrom(String.class)) { + return (T) value; + } + if (type.getRawType().isAssignableFrom(Boolean.class)) { + if (value.equalsIgnoreCase("false")) { + return (T) Boolean.valueOf(value); + } else if (value.equalsIgnoreCase("true")) { + return (T) Boolean.valueOf(value); + } + } + if (type.getRawType().isAssignableFrom(Tristate.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) Tristate.UNDEFINED; + } + if (value.equalsIgnoreCase("true")) { + return (T) Tristate.TRUE; + } + if (value.equalsIgnoreCase("false")) { + return (T) Tristate.FALSE; + } + int permValue = 0; + try { + permValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + return (T) Tristate.UNDEFINED; + } + if (permValue == 0) { + return (T) Tristate.UNDEFINED; + } + return (T) (permValue == 1 ? Tristate.TRUE : Tristate.FALSE); + } + if (type.getRawType().isAssignableFrom(GameModeType.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) GameModeTypes.UNDEFINED; + } + if (value.equalsIgnoreCase("adventure")) { + return (T) GameModeTypes.ADVENTURE; + } + if (value.equalsIgnoreCase("creative")) { + return (T) GameModeTypes.CREATIVE; + } + if (value.equalsIgnoreCase("survival")) { + return (T) GameModeTypes.SURVIVAL; + } + if (value.equalsIgnoreCase("spectator")) { + return (T) GameModeTypes.SPECTATOR; + } + } + if (type.getRawType().isAssignableFrom(WeatherType.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) WeatherTypes.UNDEFINED; + } + if (value.equalsIgnoreCase("clear")) { + return (T) WeatherTypes.CLEAR; + } + if (value.equalsIgnoreCase("rain")) { + return (T) WeatherTypes.RAIN; + } + } + if (type.getRawType().isAssignableFrom(CreateModeType.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) CreateModeTypes.UNDEFINED; + } + if (value.equalsIgnoreCase("area")) { + return (T) CreateModeTypes.AREA; + } + if (value.equalsIgnoreCase("volume")) { + return (T) CreateModeTypes.VOLUME; + } + } + return null; + } + + private Consumer<CommandSource> createClaimOptionConsumer(GDPermissionUser src, GDClaim claim, MenuType optionType) { + return consumer -> { + showOptionPermissions(src, claim, optionType); + }; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/ClaimSubjectType.java b/sponge/src/main/java/com/griefdefender/command/ClaimSubjectType.java new file mode 100644 index 0000000..af6eb97 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/ClaimSubjectType.java @@ -0,0 +1,42 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +public enum ClaimSubjectType { + + GLOBAL("Global"), + GROUP("Group"), + PLAYER("Player"); + + private String friendlyName; + + private ClaimSubjectType(String name) { + this.friendlyName = name; + } + + public String getFriendlyName() { + return this.friendlyName; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandAdjustBonusClaimBlocks.java b/sponge/src/main/java/com/griefdefender/command/CommandAdjustBonusClaimBlocks.java new file mode 100644 index 0000000..719937e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandAdjustBonusClaimBlocks.java @@ -0,0 +1,90 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.world.storage.WorldProperties; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SET_ACCRUED_CLAIM_BLOCKS) +public class CommandAdjustBonusClaimBlocks extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("acb|adjustclaimblocks") + @Description("Updates a player's accrued claim block total") + @Syntax("<player> <amount>") + @Subcommand("player adjustbonusblocks") + public void execute(CommandSource src, User user, int amount, @Optional String world) { + WorldProperties worldProperties = world == null ? null : Sponge.getServer().getWorldProperties(world).orElse(null); + if (worldProperties == null) { + if (src instanceof Player) { + worldProperties = ((Player) src).getWorld().getProperties(); + } else { + worldProperties = Sponge.getServer().getDefaultWorld().get(); + } + } + if (worldProperties == null || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(worldProperties.getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // parse the adjustment amount + int adjustment = amount; + //User user = args.<User>getOne("user").get(); + + // give blocks to player + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(worldProperties.getUniqueId(), user.getUniqueId()); + playerData.setBonusClaimBlocks(playerData.getBonusClaimBlocks() + adjustment); + playerData.getStorageData().save(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ADJUST_BONUS_BLOCKS_SUCCESS, ImmutableMap.of( + "player", user.getName(), + "amount", adjustment, + "total", playerData.getBonusClaimBlocks())); + TextAdapter.sendComponent(src, message); + GriefDefenderPlugin.getInstance().getLogger().info( + src.getName() + " adjusted " + user.getName() + "'s bonus claim blocks by " + adjustment + "."); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandCallback.java b/sponge/src/main/java/com/griefdefender/command/CommandCallback.java new file mode 100644 index 0000000..715d158 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandCallback.java @@ -0,0 +1,47 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import com.griefdefender.text.action.GDCallbackHolder; +import org.spongepowered.api.command.CommandSource; + +import java.util.UUID; +import java.util.function.Consumer; + +public class CommandCallback extends BaseCommand { + + @CommandAlias("gd:callback") + @Description("Execute a callback registered as part of a Text object. Primarily for internal use") + public void execute(CommandSource src, String[] args) { + final UUID callbackId = UUID.fromString(args[0]); + Consumer<CommandSource> callback = GDCallbackHolder.getInstance().getCallbackForUUID(callbackId).orElse(null); + if (callback != null) { + callback.accept(src); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandon.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandon.java new file mode 100644 index 0000000..bd99737 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandon.java @@ -0,0 +1,218 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_ABANDON_BASIC) +public class CommandClaimAbandon extends BaseCommand { + + protected boolean abandonTopClaim = false; + + @CommandAlias("abandon|abandonclaim") + @Description("Abandons a claim") + @Subcommand("abandon claim") + public void execute(Player player) { + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + final UUID ownerId = claim.getOwnerUniqueId(); + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDPlayerData playerData = user.getInternalPlayerData(); + + final boolean isAdmin = playerData.canIgnoreClaim(claim); + final boolean isTown = claim.isTown(); + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ABANDON_CLAIM_MISSING); + return; + } else if (!isAdmin && !player.getUniqueId().equals(ownerId) && claim.isUserTrusted(player, TrustTypes.MANAGER)) { + if (claim.parent == null) { + // Managers can only abandon child claims + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + } else if (!isAdmin && (claim.allowEdit(player) != null || (!claim.isAdminClaim() && !player.getUniqueId().equals(ownerId)))) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } else { + if (this.abandonTopClaim && claim.children.size() > 0) { + if (claim.isTown() || claim.isAdminClaim()) { + Set<Claim> invalidClaims = new HashSet<>(); + for (Claim child : claim.getChildren(true)) { + if (child.getOwnerUniqueId() == null || !child.getOwnerUniqueId().equals(ownerId)) { + //return CommandResult.empty(); + invalidClaims.add(child); + } + } + + if (!invalidClaims.isEmpty()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ABANDON_TOWN_CHILDREN); + CommandHelper.showClaims(player, invalidClaims, 0, true); + return; + } + } + } + } + + final int abandonDelay = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.ABANDON_DELAY, claim); + if (abandonDelay > 0) { + final Instant localNow = Instant.now(); + final Instant dateCreated = claim.getInternalClaimData().getDateCreated(); + final Instant delayExpires = dateCreated.plus(Duration.ofDays(abandonDelay)); + final boolean delayActive = !delayExpires.isBefore(localNow); + if (delayActive) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.ABANDON_CLAIM_DELAY_WARNING, + ImmutableMap.of("date", Date.from(delayExpires)))); + return; + } + } + final boolean autoSchematicRestore = GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()).getConfig().claim.claimAutoSchematicRestore; + final Component confirmationText = TextComponent.builder() + .append(autoSchematicRestore ? MessageCache.getInstance().SCHEMATIC_ABANDON_RESTORE_WARNING : MessageCache.getInstance().ABANDON_WARNING) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(player, playerData, claim, this.abandonTopClaim)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(player, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player player, GDPlayerData playerData, GDClaim claim, boolean abandonTopClaim) { + return confirm -> { + + GDCauseStackManager.getInstance().pushCause(player); + GDRemoveClaimEvent.Abandon event = new GDRemoveClaimEvent.Abandon(claim); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageCache.getInstance().PLUGIN_EVENT_CANCEL)); + return; + } + + if (!claim.isSubdivision() && !claim.isAdminClaim()) { + if (GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(playerData.playerID).orElse(null); + if (playerAccount == null) { + return; + } + } + } + + GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(player.getWorld().getUniqueId()); + playerData.useRestoreSchematic = event.isRestoring(); + final ClaimResult claimResult = claimManager.deleteClaimInternal(claim, abandonTopClaim); + playerData.useRestoreSchematic = false; + if (!claimResult.successful()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ABANDON_FAILED, + ImmutableMap.of("result", claimResult.getMessage().orElse(TextComponent.of(claimResult.getResultType().toString())))))); + return; + } + + // remove all context permissions + PermissionUtil.getInstance().clearPermissions(claim); + playerData.revertActiveVisual(player); + + if (claim.isTown()) { + playerData.inTown = false; + playerData.townChat = false; + } + + if (!claim.isSubdivision() && !claim.isAdminClaim()) { + final double abandonReturnRatio = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), player, Options.ABANDON_RETURN_RATIO, claim); + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(playerData.playerID).orElse(null); + if (playerAccount == null) { + return; + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final double requiredClaimBlocks = claim.getClaimBlocks() * abandonReturnRatio; + final double refund = requiredClaimBlocks * claim.getOwnerEconomyBlockCost(); + final TransactionResult result = playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(refund), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() == ResultType.SUCCESS) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_ABANDON_SUCCESS, ImmutableMap.of( + "amount", refund)); + GriefDefenderPlugin.sendMessage(player, message); + } + } else { + int newAccruedClaimCount = playerData.getAccruedClaimBlocks() - ((int) Math.ceil(claim.getClaimBlocks() * (1 - abandonReturnRatio))); + playerData.setAccruedClaimBlocks(newAccruedClaimCount); + int remainingBlocks = playerData.getRemainingClaimBlocks(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ABANDON_SUCCESS, ImmutableMap.of( + "amount", remainingBlocks)); + GriefDefenderPlugin.sendMessage(player, message); + } + } + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonAll.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonAll.java new file mode 100644 index 0000000..2f7d1d6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonAll.java @@ -0,0 +1,188 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_ABANDON_ALL_CLAIMS) +public class CommandClaimAbandonAll extends BaseCommand { + + @CommandAlias("abandonall|abandonallclaims") + @Description("Abandons ALL your claims") + @Subcommand("abandon all") + public void execute(Player player) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + int originalClaimCount = user.getInternalPlayerData().getInternalClaims().size(); + + if (originalClaimCount == 0) { + try { + throw new CommandException(MessageCache.getInstance().CLAIM_NO_CLAIMS); + } catch (CommandException e) { + TextAdapter.sendComponent(player, e.getText()); + return; + } + } + + final boolean autoSchematicRestore = GriefDefenderPlugin.getActiveConfig(player.getWorld().getUniqueId()).getConfig().claim.claimAutoSchematicRestore; + final Component confirmationText = TextComponent.builder() + .append(autoSchematicRestore ? MessageCache.getInstance().SCHEMATIC_ABANDON_ALL_RESTORE_WARNING : MessageCache.getInstance().ABANDON_ALL_WARNING) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(user)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(player, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(GDPermissionUser user) { + return confirm -> { + Set<Claim> allowedClaims = new HashSet<>(); + Set<Claim> delayedClaims = new HashSet<>(); + final int abandonDelay = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.ABANDON_DELAY); + final Player player = user.getOnlinePlayer(); + final GDPlayerData playerData = user.getInternalPlayerData(); + for (Claim claim : playerData.getInternalClaims()) { + if (abandonDelay > 0) { + final Instant localNow = Instant.now(); + final Instant dateCreated = ((GDClaim) claim).getInternalClaimData().getDateCreated(); + final Instant dateExpires = dateCreated.plus(Duration.ofDays(abandonDelay)); + final boolean delayActive = !dateExpires.isBefore(localNow); + if (delayActive) { + delayedClaims.add(claim); + } else { + allowedClaims.add(claim); + } + } else { + allowedClaims.add(claim); + } + } + + if (!allowedClaims.isEmpty()) { + GDCauseStackManager.getInstance().pushCause(user); + GDRemoveClaimEvent.Abandon event = new GDRemoveClaimEvent.Abandon(ImmutableList.copyOf(allowedClaims)); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(user.getOnlinePlayer(), event.getMessage().orElse(MessageCache.getInstance().PLUGIN_EVENT_CANCEL).color(TextColor.RED)); + return; + } + + double refund = 0; + // adjust claim blocks + for (Claim claim : allowedClaims) { + // remove all context permissions + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + if (claim.isSubdivision() || claim.isAdminClaim() || claim.isWilderness()) { + continue; + } + final double abandonReturnRatio = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), user, Options.ABANDON_RETURN_RATIO, claim); + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + refund += claim.getClaimBlocks() * abandonReturnRatio; + } else { + playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - ((int) Math.ceil(claim.getClaimBlocks() * (1 - abandonReturnRatio)))); + } + } + + playerData.useRestoreSchematic = event.isRestoring(); + GriefDefenderPlugin.getInstance().dataStore.abandonClaimsForPlayer(user, allowedClaims); + playerData.useRestoreSchematic = false; + + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(playerData.playerID).orElse(null); + if (playerAccount == null) { + return; + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final TransactionResult result = playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(refund), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() == ResultType.SUCCESS) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_ABANDON_SUCCESS, ImmutableMap.of( + "amount", TextComponent.of(String.valueOf(refund)))); + GriefDefenderPlugin.sendMessage(player, message); + } + } else { + int remainingBlocks = playerData.getRemainingClaimBlocks(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ABANDON_SUCCESS, ImmutableMap.of( + "amount", remainingBlocks)); + TextAdapter.sendComponent(player, message); + } + + playerData.revertActiveVisual(player); + } + + if (!delayedClaims.isEmpty()) { + TextAdapter.sendComponent(player, MessageCache.getInstance().ABANDON_ALL_DELAY_WARNING); + CommandHelper.showClaims(player, delayedClaims, 0, true); + } + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonTop.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonTop.java new file mode 100644 index 0000000..3ea5cc1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimAbandonTop.java @@ -0,0 +1,48 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_ABANDON_TOP_LEVEL_CLAIM) +public class CommandClaimAbandonTop extends CommandClaimAbandon { + + public CommandClaimAbandonTop() { + this.abandonTopClaim = true; + } + + @CommandAlias("abandontop") + @Description("Abandons top level claim") + @Subcommand("abandon top") + public void execute(Player player) { + super.execute(player); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimAdmin.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimAdmin.java new file mode 100644 index 0000000..c913dc2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimAdmin.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_ADMIN_CLAIMS) +public class CommandClaimAdmin extends BaseCommand { + + @CommandAlias("modeadmin|adminclaims|ac") + @Description("Switches the shovel tool to administrative claims mode") + @Subcommand("mode admin") + public void execute(Player player) { + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.shovelMode = ShovelTypes.ADMIN; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_ADMIN); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimBan.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimBan.java new file mode 100644 index 0000000..74a9fbd --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimBan.java @@ -0,0 +1,129 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.item.inventory.ItemStack; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.registry.BlockTypeRegistryModule; +import com.griefdefender.internal.registry.EntityTypeRegistryModule; +import com.griefdefender.internal.registry.ItemTypeRegistryModule; +import com.griefdefender.permission.GDPermissions; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_BAN) +public class CommandClaimBan extends BaseCommand { + + @CommandCompletion("@gdbantypes @gdmcids @gddummy") + @CommandAlias("claimban") + @Description("Bans target id from all usage.") + @Syntax("hand | <type> <target> [<message>]") + @Subcommand("ban") + public void execute(Player player, String type, @Optional String id, @Optional String message) { + Component component = null; + if (type.equalsIgnoreCase("block")) { + if (!BlockTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_BLOCK_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + if (message == null) { + component = TextComponent.empty(); + } else { + component = LegacyComponentSerializer.legacy().deserialize(message, '&'); + } + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.addBlockBan(id, component); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMBAN_SUCCESS_BLOCK, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + } else if (type.equalsIgnoreCase("entity")) { + if (!EntityTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_ENTITY_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + if (message == null) { + component = TextComponent.empty(); + } else { + component = LegacyComponentSerializer.legacy().deserialize(message, '&'); + } + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.addEntityBan(id, component); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMBAN_SUCCESS_ENTITY, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + } else if (type.equalsIgnoreCase("item")) { + if (!ItemTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_ITEM_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + if (message == null) { + component = TextComponent.empty(); + } else { + component = LegacyComponentSerializer.legacy().deserialize(message, '&'); + } + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.addItemBan(id, component); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMBAN_SUCCESS_ITEM, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + } else if (type.equalsIgnoreCase("hand")) { + final ItemStack itemInHand = player.getItemInHand(HandTypes.MAIN_HAND).orElse(null); + if (itemInHand == null) { + return; + } + final String handItemId = itemInHand.getType().getId(); + if (message == null) { + component = TextComponent.empty(); + } else { + component = LegacyComponentSerializer.legacy().deserialize(message, '&'); + } + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.addItemBan(handItemId, component); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMBAN_SUCCESS_ITEM, + ImmutableMap.of("id", TextComponent.of(handItemId, TextColor.LIGHT_PURPLE)))); + } + if (component == null) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_TYPE, + ImmutableMap.of("type", type))); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimBank.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimBank.java new file mode 100644 index 0000000..c5f16cb --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimBank.java @@ -0,0 +1,76 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_BANK) +public class CommandClaimBank extends BaseCommand { + + protected boolean townOnly = false; + + @CommandAlias("claimbank") + @Description("Used for claim bank queries") + @Syntax("<withdraw|deposit> <amount>") + @Subcommand("claim bank") + public void execute(Player player, @Optional String[] args) throws CommandException { + if (!GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()).getConfig().claim.bankTaxSystem) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().BANK_TAX_SYSTEM_DISABLED); + return; + } + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + if (this.townOnly) { + if (!claim.isInTown()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_NOT_IN); + return; + } + claim = claim.getTownClaim(); + } else { + if (claim.isSubdivision() || claim.isAdminClaim()) { + return; + } + } + + if (args.length == 0 || args.length < 2) { + CommandHelper.displayClaimBankInfo(player, claim); + return; + } + + CommandHelper.handleBankTransaction(player, args, claim); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimBasic.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimBasic.java new file mode 100644 index 0000000..bf9191f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimBasic.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_BASIC_MODE) +public class CommandClaimBasic extends BaseCommand { + + @CommandAlias("modebasic|basicclaims|bc") + @Description("Switches the shovel tool back to basic claims mode") + @Subcommand("mode basic") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.shovelMode = ShovelTypes.BASIC; + playerData.claimSubdividing = null; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_BASIC); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimBuy.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimBuy.java new file mode 100644 index 0000000..c57de0f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimBuy.java @@ -0,0 +1,97 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.account.Account; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_BUY) +public class CommandClaimBuy extends BaseCommand { + + @CommandAlias("claimbuy") + @Description("List all claims available for purchase.\nNote: Requires economy plugin.") + @Subcommand("buy claim") + public void execute(Player player) { + if (!GriefDefenderPlugin.getInstance().economyService.isPresent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + Set<Claim> claimsForSale = new HashSet<>(); + GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(player.getWorld().getUniqueId()); + for (Claim worldClaim : claimManager.getWorldClaims()) { + if (worldClaim.isWilderness()) { + continue; + } + if (!worldClaim.isAdminClaim() && worldClaim.getEconomyData().isForSale() && worldClaim.getEconomyData().getSalePrice() > -1) { + claimsForSale.add(worldClaim); + } + for (Claim child : worldClaim.getChildren(true)) { + if (child.isAdminClaim()) { + continue; + } + if (child.getEconomyData().isForSale() && child.getEconomyData().getSalePrice() > -1) { + claimsForSale.add(child); + } + } + } + + List<Component> claimsTextList = CommandHelper.generateClaimTextListCommand(new ArrayList<Component>(), claimsForSale, player.getWorld().getName(), null, player, CommandHelper.createCommandConsumer(player, "claimbuy", ""), false); + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(MessageCache.getInstance().COMMAND_CLAIMBUY_TITLE).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(claimsTextList); + paginationBuilder.sendTo(player); + return; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimBuyBlocks.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimBuyBlocks.java new file mode 100644 index 0000000..1782354 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimBuyBlocks.java @@ -0,0 +1,143 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; + +import java.math.BigDecimal; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_BUY_CLAIM_BLOCKS) +public class CommandClaimBuyBlocks extends BaseCommand { + + @CommandAlias("buyclaim|buyclaimblocks|buyblocks") + @Description("Purchases additional claim blocks with server money.\nNote: Requires economy plugin.") + @Syntax("[<amount>]") + @Subcommand("buy blocks") + public void execute(Player player, @Optional Integer blockCount) { + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + TextAdapter.sendComponent(player, MessageCache.getInstance().COMMAND_NOT_AVAILABLE_ECONOMY); + return; + } + + if (!GriefDefenderPlugin.getInstance().economyService.isPresent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + final double economyBlockCost = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), player, Options.ECONOMY_BLOCK_COST); + final double economyBlockSell = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), player, Options.ECONOMY_BLOCK_SELL_RETURN); + if (economyBlockCost == 0 && economyBlockSell == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_BUY_SELL_DISABLED); + return; + } + + if (economyBlockCost == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_ONLY_SELL); + return; + } + + double balance = playerAccount.getBalance(GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency()).doubleValue(); + if (blockCount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_PURCHASE_COST, ImmutableMap.of( + "amount", economyBlockCost, + "balance", String.valueOf("$" + balance))); + GriefDefenderPlugin.sendMessage(player, message); + return; + } else { + if (blockCount <= 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_BUY_INVALID); + return; + } + + final double totalCost = blockCount * economyBlockCost; + final int newClaimBlockTotal = playerData.getAccruedClaimBlocks() + blockCount; + if (newClaimBlockTotal > playerData.getMaxAccruedClaimBlocks()) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_PURCHASE_LIMIT, ImmutableMap.of( + "total", newClaimBlockTotal, + "limit", playerData.getMaxAccruedClaimBlocks())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + Sponge.getCauseStackManager().addContext(GriefDefenderPlugin.PLUGIN_CONTEXT, GriefDefenderPlugin.getInstance()); + TransactionResult transactionResult = playerAccount.withdraw + (GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(), BigDecimal.valueOf(totalCost), + Sponge.getCauseStackManager().getCurrentCause()); + + if (transactionResult.getResult() != ResultType.SUCCESS) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_WITHDRAW_ERROR, ImmutableMap.of( + "reason", transactionResult.getResult())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + } + + final int bonusTotal = playerData.getBonusClaimBlocks(); + playerData.setBonusClaimBlocks(bonusTotal + blockCount); + playerData.getStorageData().save(); + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_PURCHASE_CONFIRMATION, ImmutableMap.of( + "amount", totalCost, + "balance", playerData.getRemainingClaimBlocks())); + GriefDefenderPlugin.sendMessage(player, message); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimClear.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimClear.java new file mode 100644 index 0000000..009b1c3 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimClear.java @@ -0,0 +1,173 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.entity.IEntityOwnable; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.living.Villager; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.World; +import org.spongepowered.common.SpongeImplHooks; +import org.spongepowered.common.entity.SpongeEntityType; + +import java.util.UUID; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_CLEAR) +public class CommandClaimClear extends BaseCommand { + + @CommandCompletion("@gdentityids @gddummy") + @CommandAlias("claimclear") + @Description("Allows clearing of entities within one or more claims.") + @Syntax("<entity_id> [claim_uuid]") + @Subcommand("claim clear") + public void execute(Player player, String target, @Optional String claimId) { + World world = player.getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + UUID claimUniqueId = null; + GDClaim targetClaim = null; + if (claimId == null) { + targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + final Component result = targetClaim.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + claimUniqueId = targetClaim.getUniqueId(); + } else { + if (!player.hasPermission(GDPermissions.COMMAND_DELETE_CLAIMS)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CLAIMCLEAR_UUID_DENY); + return; + } + try { + claimUniqueId = UUID.fromString(claimId); + } catch (IllegalArgumentException e) { + return; + } + } + + if (targetClaim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_ACTION_NOT_AVAILABLE, + ImmutableMap.of("type", TextComponent.of("the wilderness")))); + return; + } + // Unfortunately this is required until Pixelmon registers their entities correctly in FML + // If target was not found in registry, assume its a pixelmon animal + EntityType entityType = Sponge.getRegistry().getType(EntityType.class, target).orElse(null); + boolean isPixelmonAnimal = target.contains("pixelmon") && entityType == null; + + int count = 0; + String[] parts = target.split(":"); + String modId = "minecraft"; + EnumCreatureType creatureType = null; + if (parts.length > 1) { + modId = parts[0]; + creatureType = NMSUtil.SPAWN_TYPES.get(parts[1]); + } else { + creatureType = NMSUtil.SPAWN_TYPES.get(parts[0]); + if (creatureType == null) { + target = "minecraft:" + target; + } + } + + for (Entity entity : world.getEntities()) { + net.minecraft.entity.Entity mcEntity = (net.minecraft.entity.Entity) entity; + if (entity instanceof Villager) { + continue; + } + if (entity instanceof IEntityOwnable) { + IEntityOwnable ownable = (IEntityOwnable) entity; + if (ownable.getOwnerId() != null) { + continue; + } + } + + if (isPixelmonAnimal) { + if (parts[1].equalsIgnoreCase(mcEntity.getName().toLowerCase())) { + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(entity.getLocation()); + if (claim != null && claim.getUniqueId().equals(claimUniqueId)) { + mcEntity.setDead(); + count++; + } + } + } else if (creatureType != null && SpongeImplHooks.isCreatureOfType(mcEntity, creatureType)) { + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(entity.getLocation()); + if (claim != null && claim.getUniqueId().equals(claimUniqueId)) { + // check modId + String mod = ((SpongeEntityType) entity.getType()).getModId(); + if (modId.equalsIgnoreCase(mod)) { + mcEntity.setDead(); + count++; + } + } + } else { + if (entityType == null || !entityType.equals(((SpongeEntityType) entity.getType()))) { + continue; + } + + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(entity.getLocation()); + if (claim != null && claim.getUniqueId().equals(claimUniqueId)) { + mcEntity.setDead(); + count++; + } + } + } + + if (count == 0) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_CLAIMCLEAR_NO_ENTITIES, + ImmutableMap.of("type", TextComponent.of(target, TextColor.GREEN)))); + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_CLAIMCLEAR_NO_ENTITIES, + ImmutableMap.of( + "amount", count, + "type", TextComponent.of(target, TextColor.GREEN)))); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimContract.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimContract.java new file mode 100644 index 0000000..d628b6a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimContract.java @@ -0,0 +1,201 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.property.entity.EyeLocationProperty; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.util.Direction; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PlayerUtil; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_EXPAND) +public class CommandClaimContract extends BaseCommand { + + @CommandCompletion("@gddummy @gdblockfaces @gddummy") + @CommandAlias("claimcontract|contractclaim") + @Description("Contracts/Shrinks the claim from the direction you are facing.") + @Syntax("<amount> [direction]") + @Subcommand("claim contract") + public void execute(Player player, int amount, @Optional String direction) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(user.getInternalPlayerData(), player.getLocation()); + final GDPlayerData playerData = user.getInternalPlayerData(); + + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + if (!claim.getInternalClaimData().isResizable() || (!claim.getOwnerUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_RESIZE); + return; + } + + final Vector3i lesser = claim.lesserBoundaryCorner; + final Vector3i greater = claim.greaterBoundaryCorner; + Vector3i point1 = null; + Vector3i point2 = null; + if (direction == null || !direction.equalsIgnoreCase("all")) { + final Direction face = direction == null ? PlayerUtil.getInstance().getBlockFace(player) : PlayerUtil.getInstance().getBlockFace(direction); + if (face == null || amount <= 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_INVALID); + return; + } + + if ((face == Direction.UP || face == Direction.DOWN) && !claim.cuboid) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_INVALID); + return; + } + if (face == Direction.EAST) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX() - amount, greater.getY(), greater.getZ()); + } else if (face == Direction.WEST) { + point1 = new Vector3i(lesser.getX() + amount, lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ()); + } else if (face == Direction.NORTH) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ() + amount); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ()); + } else if (face == Direction.SOUTH) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ() - amount); + } + } else { + point1 = new Vector3i( + lesser.getX() + amount, + lesser.getY(), + lesser.getZ() + amount); + point2 = new Vector3i( + greater.getX() - amount, + greater.getY(), + greater.getZ() - amount); + } + + final ClaimResult result = claim.resize(point1, point2); + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) result.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().RESIZE_OVERLAP); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showOverlapClaims(player, claims, player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY()); + } else { + // TODO add to lang + GriefDefenderPlugin.sendMessage(player, TextComponent.of("Could not resize claim. Reason : " + result.getResultType()).color(TextColor.RED)); + } + } else { + int claimBlocksRemaining = playerData.getRemainingClaimBlocks();; + if (!claim.isAdminClaim()) { + UUID ownerID = claim.getOwnerUniqueId(); + if (claim.parent != null) { + ownerID = claim.parent.getOwnerUniqueId(); + } + + if (ownerID.equals(player.getUniqueId())) { + claimBlocksRemaining = playerData.getRemainingClaimBlocks(); + } else { + GDPlayerData ownerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), ownerID); + claimBlocksRemaining = ownerData.getRemainingClaimBlocks(); + final Player owner = Sponge.getServer().getPlayer(ownerID).orElse(null); + if (owner == null || !owner.isOnline()) { + GriefDefenderPlugin.getInstance().dataStore.clearCachedPlayerData(player.getWorld().getUniqueId(), ownerID); + } + } + } + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount != null) { + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_2D, params)); + } + } + } else { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_2D, params)); + } + } + playerData.revertActiveVisual(player); + claim.getVisualizer().resetVisuals(); + claim.getVisualizer().createClaimBlockVisuals(player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(), player.getLocation(), playerData); + claim.getVisualizer().apply(player); + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().visualizeClaim(claim, player, playerData, false); + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimCreate.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimCreate.java new file mode 100644 index 0000000..457f87a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimCreate.java @@ -0,0 +1,146 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import java.util.HashSet; +import java.util.Set; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.registry.ClaimTypeRegistryModule; +import com.griefdefender.util.EconomyUtil; +import com.griefdefender.util.PlayerUtil; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.CLAIM_CREATE) +public class CommandClaimCreate extends BaseCommand { + + @CommandCompletion("@gddummy @gdclaimtypes @gddummy") + @CommandAlias("claimcreate") + @Description("Creates a claim around the player.") + @Syntax("<radius> [type] [player]") + @Subcommand("claim create") + public void execute(Player player, int radius, @Optional String type) { + final Location<World> location = player.getLocation(); + final World world = location.getExtent(); + final int minClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.MIN_LEVEL); + final int maxClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.MAX_LEVEL); + if (location.getBlockY() < minClaimLevel || location.getBlockY() > maxClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_CHEST_OUTSIDE_LEVEL, + ImmutableMap.of( + "min-level", minClaimLevel, + "max-level", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final Vector3i lesserBoundary = new Vector3i( + location.getBlockX() - radius, + minClaimLevel, + location.getBlockZ() - radius); + final Vector3i greaterBoundary = new Vector3i( + location.getBlockX() + radius, + maxClaimLevel, + location.getBlockZ() + radius); + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDPlayerData playerData = user.getInternalPlayerData(); + final ClaimType claimType = ClaimTypeRegistryModule.getInstance().getById(type).orElse(ClaimTypes.BASIC); + final boolean cuboid = playerData.getClaimCreateMode() == CreateModeTypes.VOLUME; + if ((claimType == ClaimTypes.BASIC || claimType == ClaimTypes.TOWN) && GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + EconomyUtil.getInstance().economyCreateClaimConfirmation(player, playerData, location.getBlockY(), lesserBoundary, greaterBoundary, claimType, + cuboid, playerData.claimSubdividing); + return; + } + + final ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) + .bounds(lesserBoundary, greaterBoundary) + .cuboid(cuboid) + .owner(user.getUniqueId()) + .type(claimType) + .world(world.getUniqueId()) + .build(); + GDClaim gdClaim = (GDClaim) result.getClaim().orElse(null); + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) result.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showOverlapClaims(player, claims, location.getBlockY()); + } else if (result.getResultType() == ClaimResultType.CLAIM_EVENT_CANCELLED) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_CANCEL); + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_FAILED_RESULT, + ImmutableMap.of("reason", result.getResultType()))); + } + return; + } else { + playerData.lastShovelLocation = null; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_SUCCESS, + ImmutableMap.of( + "type", gdClaim.getFriendlyNameType(true))); + GriefDefenderPlugin.sendMessage(player, message); + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().stopVisualDrag(player); + GriefDefenderPlugin.getInstance().getWorldEditProvider().visualizeClaim(gdClaim, player, playerData, false); + } + gdClaim.getVisualizer().createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + gdClaim.getVisualizer().apply(player, false); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimCuboid.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimCuboid.java new file mode 100644 index 0000000..4205422 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimCuboid.java @@ -0,0 +1,56 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CUBOID_CLAIMS) +public class CommandClaimCuboid extends BaseCommand { + + @CommandAlias("cuboid") + @Description("Toggles cuboid claims mode.") + @Subcommand("cuboid") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (playerData.getClaimCreateMode() == CreateModeTypes.AREA) { + playerData.setClaimCreateMode(CreateModeTypes.VOLUME); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CUBOID_ENABLED); + } else { + playerData.setClaimCreateMode(CreateModeTypes.AREA); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CUBOID_DISABLED); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimDelete.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimDelete.java new file mode 100644 index 0000000..1b730d6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimDelete.java @@ -0,0 +1,121 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_DELETE_CLAIMS) +public class CommandClaimDelete extends BaseCommand { + + protected boolean deleteTopLevelClaim = false; + + @CommandAlias("deleteclaim") + @Description("Deletes the claim you're standing in, even if it's not your claim.") + @Subcommand("delete claim") + public void execute(Player player) { + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_FOUND); + return; + } + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_DELETE, + ImmutableMap.of( + "type", claim.getType().getName())); + + if (claim.isAdminClaim() && !player.hasPermission(GDPermissions.DELETE_CLAIM_ADMIN)) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + if (claim.isBasicClaim() && !player.hasPermission(GDPermissions.DELETE_CLAIM_BASIC)) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final Component confirmationText = TextComponent.builder() + .append(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_CLAIM_WARNING, + ImmutableMap.of("player", claim.getOwnerName().color(TextColor.AQUA)))) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(player, claim, deleteTopLevelClaim)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(player, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player player, Claim claim, boolean deleteTopLevelClaim) { + return confirm -> { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDCauseStackManager.getInstance().pushCause(player); + ClaimResult claimResult = GriefDefenderPlugin.getInstance().dataStore.deleteClaim(claim, !deleteTopLevelClaim); + GDCauseStackManager.getInstance().popCause(); + if (!claimResult.successful()) { + GriefDefenderPlugin.sendMessage(player, claimResult.getMessage().orElse(MessageCache.getInstance().PLUGIN_EVENT_CANCEL)); + return; + } + + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + playerData.revertActiveVisual(player); + + if (claim.isTown()) { + playerData.inTown = false; + playerData.townChat = false; + } + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_CLAIM_SUCCESS, + ImmutableMap.of("player", claim.getOwnerName().color(TextColor.AQUA))); + GriefDefenderPlugin.sendMessage(player, message); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAll.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAll.java new file mode 100644 index 0000000..e213952 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAll.java @@ -0,0 +1,110 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; + +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_DELETE_CLAIMS) +public class CommandClaimDeleteAll extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("deleteall") + @Description("Delete all of another player's claims.") + @Syntax("<player>") + @Subcommand("delete all") + public void execute(Player src, User otherPlayer) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), otherPlayer.getUniqueId()); + int originalClaimCount = playerData.getInternalClaims().size(); + + if (originalClaimCount == 0) { + TextAdapter.sendComponent(src, TextComponent.of("Player " + otherPlayer.getName() + " has no claims to delete.", TextColor.RED)); + return; + } + + final Component confirmationText = TextComponent.builder("") + .append(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_ALL_PLAYER_WARNING, + ImmutableMap.of("player", TextComponent.of(otherPlayer.getName()).color(TextColor.AQUA)))) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(src, otherPlayer, playerData)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(src, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player src, User otherPlayer, GDPlayerData playerData) { + return confirm -> { + GDCauseStackManager.getInstance().pushCause(src); + GDRemoveClaimEvent event = new GDRemoveClaimEvent(ImmutableList.copyOf(playerData.getInternalClaims())); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + GriefDefenderPlugin.sendMessage(src, event.getMessage().orElse(MessageCache.getInstance().PLUGIN_EVENT_CANCEL)); + return; + } + + GriefDefenderPlugin.getInstance().dataStore.deleteClaimsForPlayer(otherPlayer.getUniqueId()); + if (src != null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_ALL_PLAYER_SUCCESS, ImmutableMap.of( + "player", TextComponent.of(otherPlayer.getName()).color(TextColor.AQUA))); + GriefDefenderPlugin.sendMessage(src, message); + // revert any current visualization + playerData.revertActiveVisual(src); + } + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAllAdmin.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAllAdmin.java new file mode 100644 index 0000000..2be0f54 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteAllAdmin.java @@ -0,0 +1,90 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_DELETE_ADMIN_CLAIMS) +public class CommandClaimDeleteAllAdmin extends BaseCommand { + + @CommandAlias("deletealladmin") + @Description("Deletes all administrative claims.") + @Subcommand("delete alladmin") + public void execute(Player player) { + final Component confirmationText = TextComponent.builder("") + .append(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_ALL_TYPE_WARNING, + ImmutableMap.of("type", TextComponent.of("ADMIN").color(TextColor.RED)))) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(player)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(player, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player player) { + return confirm -> { + ClaimResult claimResult = GriefDefenderPlugin.getInstance().dataStore.deleteAllAdminClaims(player, player.getWorld()); + if (!claimResult.successful()) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_TYPE_NOT_FOUND, + ImmutableMap.of( + "type", ClaimTypes.ADMIN.getName().toLowerCase())); + GriefDefenderPlugin.sendMessage(player, claimResult.getMessage().orElse(message)); + return; + } + + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_ALL_TYPE_SUCCESS, + ImmutableMap.of("type", TextComponent.of("ADMIN").color(TextColor.RED)))); + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.revertActiveVisual(player); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteTop.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteTop.java new file mode 100644 index 0000000..a5a901f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimDeleteTop.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_DELETE_CLAIMS) +public class CommandClaimDeleteTop extends CommandClaimDelete { + + + public CommandClaimDeleteTop() { + this.deleteTopLevelClaim = true; + } + + @CommandAlias("deletetop") + @Description("Deletes the claim you're standing in, even if it's not your claim.") + @Subcommand("delete top") + public void execute(Player player) { + super.execute(player); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimExpand.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimExpand.java new file mode 100644 index 0000000..4442d94 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimExpand.java @@ -0,0 +1,201 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.property.entity.EyeLocationProperty; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.util.Direction; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PlayerUtil; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_EXPAND) +public class CommandClaimExpand extends BaseCommand { + + @CommandCompletion("@gddummy @gdDirections @gddummy") + @CommandAlias("claimexpand|expandclaim") + @Description("Expands the claim in the direction you are facing.") + @Syntax("<amount> [direction]") + @Subcommand("claim expand") + public void execute(Player player, int amount, @Optional String direction) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(user.getInternalPlayerData(), player.getLocation()); + final GDPlayerData playerData = user.getInternalPlayerData(); + + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + if (!claim.getInternalClaimData().isResizable() || (!claim.getOwnerUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_RESIZE); + return; + } + + final Vector3i lesser = claim.lesserBoundaryCorner; + final Vector3i greater = claim.greaterBoundaryCorner; + Vector3i point1 = null; + Vector3i point2 = null; + if (direction == null || !direction.equalsIgnoreCase("all")) { + final Direction face = direction == null ? PlayerUtil.getInstance().getBlockFace(player) : PlayerUtil.getInstance().getBlockFace(direction); + if (face == null || amount <= 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_INVALID); + return; + } + + if ((face == Direction.UP || face == Direction.DOWN) && !claim.cuboid) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_INVALID); + return; + } + if (face == Direction.EAST) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX() + amount, greater.getY(), greater.getZ()); + } else if (face == Direction.WEST) { + point1 = new Vector3i(lesser.getX() - amount, lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ()); + } else if (face == Direction.NORTH) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ() - amount); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ()); + } else if (face == Direction.SOUTH) { + point1 = new Vector3i(lesser.getX(), lesser.getY(), lesser.getZ()); + point2 = new Vector3i(greater.getX(), greater.getY(), greater.getZ() + amount); + } + } else { + point1 = new Vector3i( + lesser.getX() - amount, + lesser.getY(), + lesser.getZ() - amount); + point2 = new Vector3i( + greater.getX() + amount, + greater.getY(), + greater.getZ() + amount); + } + + final ClaimResult result = claim.resize(point1, point2); + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) result.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().RESIZE_OVERLAP); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showOverlapClaims(player, claims, player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY()); + } else { + // TODO add to lang + GriefDefenderPlugin.sendMessage(player, TextComponent.of("Could not resize claim. Reason : " + result.getResultType()).color(TextColor.RED)); + } + } else { + int claimBlocksRemaining = playerData.getRemainingClaimBlocks();; + if (!claim.isAdminClaim()) { + UUID ownerID = claim.getOwnerUniqueId(); + if (claim.parent != null) { + ownerID = claim.parent.getOwnerUniqueId(); + } + + if (ownerID.equals(player.getUniqueId())) { + claimBlocksRemaining = playerData.getRemainingClaimBlocks(); + } else { + GDPlayerData ownerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), ownerID); + claimBlocksRemaining = ownerData.getRemainingClaimBlocks(); + final Player owner = Sponge.getServer().getPlayer(ownerID).orElse(null); + if (owner == null || !owner.isOnline()) { + GriefDefenderPlugin.getInstance().dataStore.clearCachedPlayerData(player.getWorld().getUniqueId(), ownerID); + } + } + } + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount != null) { + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_2D, params)); + } + } + } else { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_2D, params)); + } + } + playerData.revertActiveVisual(player); + claim.getVisualizer().resetVisuals(); + claim.getVisualizer().createClaimBlockVisuals(player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(), player.getLocation(), playerData); + claim.getVisualizer().apply(player); + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().visualizeClaim(claim, player, playerData, false); + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFarewell.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFarewell.java new file mode 100644 index 0000000..706f80d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFarewell.java @@ -0,0 +1,80 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SET_CLAIM_FAREWELL) +public class CommandClaimFarewell extends BaseCommand { + + @CommandAlias("claimfarewell") + @Description("Sets the farewell message of your claim.") + @Syntax("<message>") + @Subcommand("claim farewell") + public void execute(Player player, String message) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (claim.allowEdit(player) != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_EDIT_CLAIM); + return; + } + + TextComponent farewell = LegacyComponentSerializer.legacy().deserialize(message, '&'); + if (farewell == TextComponent.empty() || farewell.content().equals("clear")) { + claim.getInternalClaimData().setFarewell(null); + } else { + claim.getInternalClaimData().setFarewell(farewell); + } + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + Component resultMessage = null; + if (!claim.getInternalClaimData().getFarewell().isPresent()) { + resultMessage = MessageCache.getInstance().CLAIM_FAREWELL_CLEAR; + } else { + resultMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_FAREWELL, + ImmutableMap.of( + "farewell", farewell)); + } + TextAdapter.sendComponent(player, resultMessage); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFlag.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlag.java new file mode 100644 index 0000000..484811d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlag.java @@ -0,0 +1,57 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_FLAGS_CLAIM) +public class CommandClaimFlag extends ClaimFlagBase { + + public CommandClaimFlag() { + super(ClaimSubjectType.GLOBAL); + } + + @CommandCompletion("@gdflags @gdmcids @gdtristates @gdcontexts @gddummy") + @CommandAlias("cf|claimflag") + @Description("Gets/Sets claim flags in the claim you are standing in.") + @Syntax("<flag> <target> <value> [context[key=value]]") + @Subcommand("flag claim") + public void execute(Player player, @Optional String[] args) throws InvalidCommandArgument { + this.subject = GriefDefenderPlugin.DEFAULT_HOLDER; + this.friendlySubjectName = "ALL"; + super.execute(player, args); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagDebug.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagDebug.java new file mode 100644 index 0000000..8127c26 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagDebug.java @@ -0,0 +1,67 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_FLAGS_DEBUG) +public class CommandClaimFlagDebug extends BaseCommand { + + @CommandAlias("cfd") + @Description("Toggles claim flag debug mode.") + @Subcommand("claim debug") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final Component result = claim.allowEdit(user, true); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + playerData.debugClaimPermissions = !playerData.debugClaimPermissions; + + if (!playerData.debugClaimPermissions) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CLAIMFLAGDEBUG_DISABLED); + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CLAIMFLAGDEBUG_ENABLED); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagGroup.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagGroup.java new file mode 100644 index 0000000..9c5399b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagGroup.java @@ -0,0 +1,80 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_FLAGS_GROUP) +public class CommandClaimFlagGroup extends ClaimFlagBase { + + public CommandClaimFlagGroup() { + super(ClaimSubjectType.GROUP); + } + + @CommandCompletion("@gdgroups @gdflags @gdmcids @gdtristates @gdcontexts @gddummy") + @CommandAlias("cfg") + @Description("Gets/Sets flag permission for a group in claim you are standing in.") + @Syntax("<group> <flag> <target> <value> [context[key=value]]") + @Subcommand("flag group") + public void execute(Player player, String group, @Optional String[] args) throws InvalidCommandArgument { + if (args.length < 2 || args.length > 3) { + throw new InvalidCommandArgument(); + } + + if (!PermissionUtil.getInstance().hasGroupSubject(group)) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_INVALID_GROUP, ImmutableMap.of( + "group", group)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + /*String reason = ctx.<String>getOne("reason").orElse(null); + Text reasonText = null; + if (reason != null) { + reasonText = TextSerializers.FORMATTING_CODE.deserialize(reason); + }*/ + + this.subject = PermissionHolderCache.getInstance().getOrCreateHolder(group); + this.friendlySubjectName = group; + + super.execute(player, args); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagPlayer.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagPlayer.java new file mode 100644 index 0000000..2788e3e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagPlayer.java @@ -0,0 +1,66 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_FLAGS_PLAYER) +public class CommandClaimFlagPlayer extends ClaimFlagBase { + + public CommandClaimFlagPlayer() { + super(ClaimSubjectType.PLAYER); + } + + @CommandCompletion("@gdplayers @gdflags @gdmcids @gdtristates @gdcontexts @gddummy") + @CommandAlias("cfp") + @Description("Gets/Sets flag permission for a player in claim you are standing in.") + @Syntax("<player> <flag> <target> <value> [context[key=value]]") + @Subcommand("flag player") + public void execute(Player player, User user, @Optional String[] args) throws InvalidCommandArgument { + this.subject = PermissionHolderCache.getInstance().getOrCreateUser(user); + this.friendlySubjectName = user.getName(); + + if (user.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS) && !player.hasPermission(GDPermissions.SET_ADMIN_FLAGS)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_PLAYER_ADMIN_FLAGS); + return; + } + + super.execute(player, args); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagReset.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagReset.java new file mode 100644 index 0000000..114abe9 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimFlagReset.java @@ -0,0 +1,102 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.context.Context; + +import java.util.Set; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_FLAGS_RESET) +public class CommandClaimFlagReset extends BaseCommand { + + @CommandAlias("cfr") + @Description("Resets a claim to flag defaults.") + @Subcommand("flag reset") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_RESET_FLAGS, + ImmutableMap.of( + "type", claim.getType().getName())); + if (claim.isWilderness()) { + if (!player.hasPermission(GDPermissions.MANAGE_WILDERNESS)) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + } else if (claim.isAdminClaim()) { + if (!player.getUniqueId().equals(claim.getOwnerUniqueId()) && !player.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS)) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + } else if (!player.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS) && (claim.isBasicClaim() || claim.isSubdivision()) && !player.getUniqueId().equals(claim.getOwnerUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_RESET_FLAGS_SELF); + return; + } + + final Component confirmationText = TextComponent.builder() + .append(MessageCache.getInstance().FLAG_RESET_WARNING) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(player, claim)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(player, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player player, Claim claim) { + return confirm -> { + // Remove persisted data + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().FLAG_RESET_SUCCESS); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimGreeting.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimGreeting.java new file mode 100644 index 0000000..85165fb --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimGreeting.java @@ -0,0 +1,81 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SET_CLAIM_GREETING) +public class CommandClaimGreeting extends BaseCommand { + + @CommandAlias("claimgreeting") + @Description("Sets the greeting message of your claim.") + @Syntax("<message>") + @Subcommand("claim greeting") + public void execute(Player player, String message) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component result = claim.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + final TextComponent greeting = LegacyComponentSerializer.legacy().deserialize(message, '&'); + if (greeting == TextComponent.empty() || greeting.content().equals("clear")) { + claim.getInternalClaimData().setGreeting(null); + } else { + claim.getInternalClaimData().setGreeting(greeting); + } + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + Component resultMessage = null; + if (!claim.getInternalClaimData().getGreeting().isPresent()) { + resultMessage = MessageCache.getInstance().CLAIM_GREETING_CLEAR; + } else { + resultMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_GREETING, + ImmutableMap.of( + "greeting", greeting)); + } + TextAdapter.sendComponent(player, resultMessage); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimIgnore.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimIgnore.java new file mode 100644 index 0000000..0198433 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimIgnore.java @@ -0,0 +1,67 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_IGNORE_CLAIMS) +public class CommandClaimIgnore extends BaseCommand { + + @CommandAlias("claimignore|ignoreclaims|ic") + @Description("Toggles ignore claims mode.") + @Subcommand("claim ignore") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + if (claim.isBasicClaim() && !playerData.ignoreBasicClaims || claim.isWilderness() && !playerData.ignoreWilderness || claim.isAdminClaim() && !playerData.ignoreAdminClaims) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_IGNORE, ImmutableMap.of( + "type", claim.getType().getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + playerData.ignoreClaims = !playerData.ignoreClaims; + + if (!playerData.ignoreClaims) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_RESPECTING); + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_IGNORE); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimInfo.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimInfo.java new file mode 100644 index 0000000..c553ea1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimInfo.java @@ -0,0 +1,888 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PlayerUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_INFO_BASE) +public class CommandClaimInfo extends BaseCommand { + + private final Component NONE = TextComponent.of("none", TextColor.GRAY); + private final int ADMIN_SETTINGS = 0; + private final int CLAIM_EXPIRATION = 1; + private final int DENY_MESSAGES = 2; + private final int FLAG_OVERRIDES = 3; + private final int INHERIT_PARENT = 4; + private final int PVP_OVERRIDE = 5; + private final int RAID_OVERRIDE = 6; + private final int RESIZABLE = 7; + private final int REQUIRES_CLAIM_BLOCKS = 8; + private final int SIZE_RESTRICTIONS = 9; + private final int FOR_SALE = 10; + private boolean useTownInfo = false; + + public CommandClaimInfo() { + + } + + public CommandClaimInfo(boolean useTownInfo) { + this.useTownInfo = useTownInfo; + } + + @CommandAlias("claiminfo") + @Syntax("[claim_uuid]") + @Subcommand("claim info") + public void execute(CommandSource src, String[] args) { + String claimIdentifier = null; + if (args.length > 0) { + claimIdentifier = args[0]; + } + + Player player = null; + if (src instanceof Player) { + player = (Player) src; + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + } + + if (player == null && claimIdentifier == null) { + TextAdapter.sendComponent(src, MessageCache.getInstance().COMMAND_CLAIMINFO_NOT_FOUND); + return; + } + + boolean isAdmin = src.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS); + final GDPlayerData playerData = player != null ? GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()) : null; + Claim claim = null; + if (claimIdentifier == null) { + if (player != null) { + claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + } else { + TextAdapter.sendComponent(src, MessageCache.getInstance().COMMAND_CLAIMINFO_UUID_REQUIRED); + return; + } + } else { + for (World world : Sponge.getServer().getWorlds()) { + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + continue; + } + + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + UUID uuid = null; + try { + uuid = UUID.fromString(claimIdentifier); + claim = claimManager.getClaimByUUID(uuid).orElse(null); + if (claim != null) { + break; + } + } catch (IllegalArgumentException e) { + + } + if (uuid == null) { + final List<Claim> claimList = claimManager.getClaimsByName(claimIdentifier); + if (!claimList.isEmpty()) { + claim = claimList.get(0); + } + } + } + } + + if (claim == null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_NOT_FOUND); + return; + } + + if (this.useTownInfo) { + if (!claim.isInTown()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_NOT_IN); + return; + } + claim = claim.getTown().get(); + } + + final GDClaim gdClaim = (GDClaim) claim; + final GDPermissionUser owner = PermissionHolderCache.getInstance().getOrCreateUser(claim.getOwnerUniqueId()); + final UUID ownerUniqueId = claim.getOwnerUniqueId(); + + if (!isAdmin) { + isAdmin = playerData.canIgnoreClaim(gdClaim); + } + // if not owner of claim, validate perms + if (!isAdmin && !player.getUniqueId().equals(claim.getOwnerUniqueId())) { + if (!gdClaim.getInternalClaimData().getContainers().contains(player.getUniqueId()) + && !gdClaim.getInternalClaimData().getBuilders().contains(player.getUniqueId()) + && !gdClaim.getInternalClaimData().getManagers().contains(player.getUniqueId()) + && !player.hasPermission(GDPermissions.COMMAND_CLAIM_INFO_OTHERS)) { + TextAdapter.sendComponent(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + } + + final Component allowEdit = gdClaim.allowEdit(player); + + List<Component> textList = new ArrayList<>(); + Component name = claim.getName().orElse(null); + Component greeting = claim.getData().getGreeting().orElse(null); + Component farewell = claim.getData().getFarewell().orElse(null); + String accessors = ""; + String builders = ""; + String containers = ""; + String managers = ""; + String accessorGroups = ""; + String builderGroups = ""; + String containerGroups = ""; + String managerGroups = ""; + + final int minClaimLevel = gdClaim.getOwnerMinClaimLevel(); + double claimY = gdClaim.getOwnerPlayerData() == null ? 65.0D : (minClaimLevel > 65.0D ? minClaimLevel : 65); + if (gdClaim.isCuboid()) { + claimY = gdClaim.lesserBoundaryCorner.getY(); + } + + Location<World> southWest = new Location<>(gdClaim.getWorld(), gdClaim.lesserBoundaryCorner.getX(), claimY, gdClaim.greaterBoundaryCorner.getZ()); + Location<World> northWest = new Location<>(gdClaim.getWorld(), gdClaim.lesserBoundaryCorner.getX(), claimY, gdClaim.lesserBoundaryCorner.getZ()); + Location<World> southEast = new Location<>(gdClaim.getWorld(), gdClaim.greaterBoundaryCorner.getX(), claimY, gdClaim.greaterBoundaryCorner.getZ()); + Location<World> northEast = new Location<>(gdClaim.getWorld(), gdClaim.greaterBoundaryCorner.getX(), claimY, gdClaim.lesserBoundaryCorner.getZ()); + // String southWestCorner = + Date created = null; + Date lastActive = null; + try { + Instant instant = claim.getData().getDateCreated(); + created = Date.from(instant); + } catch(DateTimeParseException ex) { + // ignore + } + + try { + Instant instant = claim.getData().getDateLastActive(); + lastActive = Date.from(instant); + } catch(DateTimeParseException ex) { + // ignore + } + + final int sizeX = Math.abs(claim.getGreaterBoundaryCorner().getX() - claim.getLesserBoundaryCorner().getX()) + 1; + final int sizeY = Math.abs(claim.getGreaterBoundaryCorner().getY() - claim.getLesserBoundaryCorner().getY()) + 1; + final int sizeZ = Math.abs(claim.getGreaterBoundaryCorner().getZ() - claim.getLesserBoundaryCorner().getZ()) + 1; + Component claimSize = TextComponent.empty(); + if (claim.isCuboid()) { + claimSize = TextComponent.builder(" ") + .append(MessageCache.getInstance().LABEL_AREA.color(TextColor.YELLOW)) + .append(": ", TextColor.YELLOW) + .append(sizeX + "x" + sizeY + "x" + sizeZ, TextColor.GRAY).build(); + } else { + claimSize = TextComponent.builder(" ") + .append(MessageCache.getInstance().LABEL_AREA.color(TextColor.YELLOW)) + .append(": ", TextColor.YELLOW) + .append(sizeX + "x" + sizeZ, TextColor.GRAY).build(); + } + final Component claimCost = TextComponent.builder(" ") + .append(MessageCache.getInstance().LABEL_BLOCKS.color(TextColor.YELLOW)) + .append(": ", TextColor.YELLOW) + .append(String.valueOf(claim.getClaimBlocks()), TextColor.GRAY).build(); + if (claim.isWilderness() && name == null) { + name = TextComponent.of("Wilderness", TextColor.GREEN); + } + Component claimName = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_NAME.color(TextColor.YELLOW)) + .append(" : ", TextColor.YELLOW) + .append(name == null ? NONE : name).build(); + Component worldName = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_WORLD.color(TextColor.YELLOW)) + .append(" : ") + .append(gdClaim.getWorld().getName(), TextColor.GRAY).build(); + if (!claim.isWilderness() && !claim.isAdminClaim()) { + claimName = TextComponent.builder() + .append(claimName) + .append(" ") + .append(worldName) + .append(claimSize) + .append(claimCost).build(); + } + // users + final List<UUID> accessorList = gdClaim.getUserTrustList(TrustTypes.ACCESSOR, true); + final List<UUID> builderList = gdClaim.getUserTrustList(TrustTypes.BUILDER, true); + final List<UUID> containerList = gdClaim.getUserTrustList(TrustTypes.CONTAINER, true); + final List<UUID> managerList = gdClaim.getUserTrustList(TrustTypes.MANAGER, true); + for (UUID uuid : accessorList) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + final String userName = user.getFriendlyName(); + if (userName != null) { + accessors += userName + " "; + } + } + for (UUID uuid : builderList) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + final String userName = user.getFriendlyName(); + if (userName != null) { + builders += userName + " "; + } + } + for (UUID uuid : containerList) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + final String userName = user.getFriendlyName(); + if (userName != null) { + containers += userName + " "; + } + } + for (UUID uuid : managerList) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + final String userName = user.getFriendlyName(); + if (userName != null) { + managers += userName + " "; + } + } + + // groups + for (String group : gdClaim.getGroupTrustList(TrustTypes.ACCESSOR, true)) { + accessorGroups += group + " "; + } + for (String group : gdClaim.getGroupTrustList(TrustTypes.BUILDER, true)) { + builderGroups += group + " "; + } + for (String group : gdClaim.getGroupTrustList(TrustTypes.CONTAINER, true)) { + containerGroups += group + " "; + } + for (String group : gdClaim.getGroupTrustList(TrustTypes.MANAGER, true)) { + managerGroups += group + " "; + } + + /*if (gpClaim.isInTown()) { + Text returnToClaimInfo = Text.builder().append(Text.of( + TextColors.WHITE, "\n[", TextColors.AQUA, "Return to standard settings", TextColors.WHITE, "]\n")) + .onClick(TextActions.executeCallback(CommandHelper.createCommandConsumer(src, "claiminfo", ""))).build(); + Text townName = Text.of(TextColors.YELLOW, "Name", TextColors.WHITE, " : ", TextColors.RESET, + gpClaim.getTownClaim().getTownData().getName().orElse(NONE)); + Text townTag = Text.of(TextColors.YELLOW, "Tag", TextColors.WHITE, " : ", TextColors.RESET, + gpClaim.getTownClaim().getTownData().getTownTag().orElse(NONE)); + townTextList.add(returnToClaimInfo); + townTextList.add(townName); + townTextList.add(townTag); + Text townSettings = Text.builder() + .append(Text.of(TextStyles.ITALIC, TextColors.GREEN, TOWN_SETTINGS)) + .onClick(TextActions.executeCallback(createSettingsConsumer(src, claim, townTextList, ClaimTypes.TOWN))) + .onHover(TextActions.showText(Text.of("Click here to view town settings"))) + .build(); + textList.add(townSettings); + }*/ + + if (isAdmin) { + Component adminSettings = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_ADMIN_SETTINGS).decoration(TextDecoration.ITALIC, true).color(TextColor.RED) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createSettingsConsumer(src, claim, generateAdminSettings(src, gdClaim), ClaimTypes.ADMIN)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().CLAIMINFO_UI_CLICK_ADMIN)) + .build(); + textList.add(adminSettings); + } + + Component bankInfo = null; + Component forSaleText = null; + if (GriefDefenderPlugin.getInstance().economyService.isPresent()) { + if (GriefDefenderPlugin.getActiveConfig(gdClaim.getWorld().getProperties()).getConfig().claim.bankTaxSystem) { + bankInfo = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_BANK_INFO.color(TextColor.GOLD).decoration(TextDecoration.ITALIC, true)) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().CLAIMINFO_UI_BANK_INFO)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(Consumer -> { CommandHelper.displayClaimBankInfo(src, gdClaim, gdClaim.isTown() ? true : false, true); }))) + .build(); + } + forSaleText = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_FOR_SALE.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, FOR_SALE, claim.getEconomyData().isForSale() ? MessageCache.getInstance().LABEL_YES.color(TextColor.GREEN) : MessageCache.getInstance().LABEL_NO.color(TextColor.GRAY))).build(); + if (claim.getEconomyData().isForSale()) { + forSaleText = TextComponent.builder() + .append(forSaleText) + .append(" ") + .append(MessageCache.getInstance().LABEL_PRICE.color(TextColor.YELLOW)) + .append(" : ") + .append(String.valueOf(claim.getEconomyData().getSalePrice()), TextColor.GOLD) + .build(); + } + } + + Component claimId = TextComponent.builder() + .append("UUID", TextColor.YELLOW) + .append(" : ") + .append(TextComponent.builder() + .append(claim.getUniqueId().toString(), TextColor.GRAY) + .insertion(claim.getUniqueId().toString()).build()).build(); + final String ownerName = PlayerUtil.getInstance().getUserName(ownerUniqueId); + Component ownerLine = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_OWNER.color(TextColor.YELLOW)) + .append(" : ") + .append(ownerName != null && !claim.isAdminClaim() && !claim.isWilderness() ? ownerName : "administrator", TextColor.GOLD).build(); + Component adminShowText = TextComponent.empty(); + Component basicShowText = TextComponent.empty(); + Component subdivisionShowText = TextComponent.empty(); + Component townShowText = TextComponent.empty(); + Component claimType = TextComponent.empty(); + final Component whiteOpenBracket = TextComponent.of("["); + final Component whiteCloseBracket = TextComponent.of("]"); + Component defaultTypeText = TextComponent.builder() + .append(whiteOpenBracket) + .append(gdClaim.getFriendlyNameType(true)) + .append(whiteCloseBracket).build(); + if (allowEdit != null && !isAdmin) { + adminShowText = allowEdit; + basicShowText = allowEdit; + subdivisionShowText = allowEdit; + townShowText = allowEdit; + Component adminTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.ADMIN ? + defaultTypeText : TextComponent.of("ADMIN", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(adminShowText)).build(); + Component basicTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.BASIC ? + defaultTypeText : TextComponent.of("BASIC", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(basicShowText)).build(); + Component subTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.SUBDIVISION ? + defaultTypeText : TextComponent.of("SUBDIVISION", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(subdivisionShowText)).build(); + Component townTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.TOWN ? + defaultTypeText : TextComponent.of("TOWN", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(townShowText)).build(); + claimType = TextComponent.builder() + .append(claim.isCuboid() ? "3D " : "2D ", TextColor.GREEN) + .append(adminTypeText) + .append(" ") + .append(basicTypeText) + .append(" ") + .append(subTypeText) + .append(" ") + .append(townTypeText) + .build(); + } else { + Component adminTypeText = defaultTypeText; + Component basicTypeText = defaultTypeText; + Component subTypeText = defaultTypeText; + Component townTypeText = defaultTypeText; + if (!claim.isAdminClaim()) { + final Component message = ((GDClaim) claim).validateClaimType(ClaimTypes.ADMIN, ownerUniqueId, playerData).getMessage().orElse(null); + adminShowText = message != null ? message : MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_CLICK_CHANGE_CLAIM, + ImmutableMap.of("type", TextComponent.of("ADMIN ", TextColor.RED))); + + if (message == null) { + adminTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.ADMIN ? + defaultTypeText : TextComponent.of("ADMIN", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimTypeConsumer(src, claim, ClaimTypes.ADMIN, isAdmin)))) + .hoverEvent(HoverEvent.showText(adminShowText)).build(); + } else { + adminTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.ADMIN ? + defaultTypeText : TextComponent.of("ADMIN", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(adminShowText)).build(); + } + } + if (!claim.isBasicClaim()) { + final Component message = ((GDClaim) claim).validateClaimType(ClaimTypes.BASIC, ownerUniqueId, playerData).getMessage().orElse(null); + basicShowText = message != null ? message : MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_CLICK_CHANGE_CLAIM, + ImmutableMap.of("type", TextComponent.of("BASIC ", TextColor.YELLOW))); + + if (message == null) { + basicTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.BASIC ? defaultTypeText : TextComponent.of("BASIC", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimTypeConsumer(src, claim, ClaimTypes.BASIC, isAdmin)))) + .hoverEvent(HoverEvent.showText(basicShowText)).build(); + } else { + basicTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.BASIC ? defaultTypeText : TextComponent.of("BASIC", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(basicShowText)).build(); + } + } + if (!claim.isSubdivision()) { + final Component message = ((GDClaim) claim).validateClaimType(ClaimTypes.SUBDIVISION, ownerUniqueId, playerData).getMessage().orElse(null); + subdivisionShowText = message != null ? message : MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_CLICK_CHANGE_CLAIM, + ImmutableMap.of("type", TextComponent.of("SUBDIVISION ", TextColor.AQUA))); + + if (message == null) { + subTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.SUBDIVISION ? defaultTypeText : TextComponent.of("SUBDIVISION", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimTypeConsumer(src, claim, ClaimTypes.SUBDIVISION, isAdmin)))) + .hoverEvent(HoverEvent.showText(subdivisionShowText)).build(); + } else { + subTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.SUBDIVISION ? defaultTypeText : TextComponent.of("SUBDIVISION", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(subdivisionShowText)).build(); + } + } + if (!claim.isTown()) { + final Component message = ((GDClaim) claim).validateClaimType(ClaimTypes.TOWN, ownerUniqueId, playerData).getMessage().orElse(null); + townShowText = message != null ? message : MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_CLICK_CHANGE_CLAIM, + ImmutableMap.of("type", TextComponent.of("TOWN ", TextColor.GREEN))); + + if (message == null) { + townTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.TOWN ? defaultTypeText : TextComponent.of("TOWN", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimTypeConsumer(src, claim, ClaimTypes.TOWN, isAdmin)))) + .hoverEvent(HoverEvent.showText(townShowText)).build(); + } else { + townTypeText = TextComponent.builder() + .append(claim.getType() == ClaimTypes.TOWN ? defaultTypeText : TextComponent.of("TOWN", TextColor.GRAY)) + .hoverEvent(HoverEvent.showText(townShowText)).build(); + } + } + + claimType = TextComponent.builder() + .append(claim.isCuboid() ? "3D " : "2D ", TextColor.GREEN) + .append(adminTypeText) + .append(" ") + .append(basicTypeText) + .append(" ") + .append(subTypeText) + .append(" ") + .append(townTypeText) + .build(); + } + + Component claimTypeInfo = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_TYPE.color(TextColor.YELLOW)) + .append(" : ") + .append(claimType).build(); + Component claimInherit = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_INHERIT.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, INHERIT_PARENT, claim.getData().doesInheritParent() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component claimExpired = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_EXPIRED.color(TextColor.YELLOW)) + .append(" : ") + .append(claim.getData().isExpired() ? TextComponent.of("YES", TextColor.RED) : TextComponent.of("NO", TextColor.GRAY)).build(); + Component claimFarewell = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_FAREWELL.color(TextColor.YELLOW)) + .append(" : ") + .append(farewell == null ? NONE : farewell).build(); + Component claimGreeting = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_GREETING.color(TextColor.YELLOW)) + .append(" : ") + .append(greeting == null ? NONE : greeting).build(); + Component claimDenyMessages = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_DENY_MESSAGES.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, DENY_MESSAGES, claim.getData().allowDenyMessages() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component pvpSetting = TextComponent.of("UNDEFINED", TextColor.GRAY); + if (claim.getData().getPvpOverride() == Tristate.TRUE) { + pvpSetting = TextComponent.of("ON", TextColor.GREEN); + } else if (claim.getData().getPvpOverride() == Tristate.FALSE) { + pvpSetting = TextComponent.of("OFF", TextColor.RED); + } + Component claimPvP = TextComponent.builder() + .append("PvP", TextColor.YELLOW) + .append(" : ") + .append(getClickableInfoText(src, claim, PVP_OVERRIDE, pvpSetting)).build(); + /* Component claimRaid = TextComponent.builder() + .append("Raid", TextColor.YELLOW) + .append(" : ") + .append(getClickableInfoText(src, claim, RAID_OVERRIDE, GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Boolean.class), owner, Options.RAID, gdClaim) == true ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + */ + Component claimSpawn = null; + if (claim.getData().getSpawnPos().isPresent()) { + Vector3i spawnPos = claim.getData().getSpawnPos().get(); + Location<World> spawnLoc = new Location<>(gdClaim.getWorld(), spawnPos.getX(), spawnPos.getY(), spawnPos.getZ()); + claimSpawn = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_SPAWN.color(TextColor.GREEN)) + .append(" : ") + .append(spawnPos.toString(), TextColor.GRAY) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(player, spawnLoc, claim, true)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().CLAIMINFO_UI_TELEPORT_SPAWN)) + .build(); + } + Component southWestCorner = TextComponent.builder() + .append("SW", TextColor.LIGHT_PURPLE) + .append(" : ") + .append(VecHelper.toVector3i(southWest).toString(), TextColor.GRAY) + .append(" ") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(player, southWest, claim)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_TELEPORT_DIRECTION, + ImmutableMap.of("direction", TextComponent.of("SW").color(TextColor.AQUA))))) + .build(); + Component southEastCorner = TextComponent.builder() + .append("SE", TextColor.LIGHT_PURPLE) + .append(" : ") + .append(VecHelper.toVector3i(southEast).toString(), TextColor.GRAY) + .append(" ") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(player, southEast, claim)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_TELEPORT_DIRECTION, + ImmutableMap.of("direction", TextComponent.of("SE").color(TextColor.AQUA))))) + .build(); + Component southCorners = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_SOUTH_CORNERS.color(TextColor.YELLOW)) + .append(" : ") + .append(southWestCorner) + .append(southEastCorner).build(); + Component northWestCorner = TextComponent.builder() + .append("NW", TextColor.LIGHT_PURPLE) + .append(" : ") + .append(VecHelper.toVector3i(northWest).toString(), TextColor.GRAY) + .append(" ") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(player, northWest, claim)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_TELEPORT_DIRECTION, + ImmutableMap.of("direction", TextComponent.of("NW").color(TextColor.AQUA))))) + .build(); + Component northEastCorner = TextComponent.builder() + .append("NE", TextColor.LIGHT_PURPLE) + .append(" : ") + .append(VecHelper.toVector3i(northEast).toString(), TextColor.GRAY) + .append(" ") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(player, northEast, claim)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMINFO_UI_TELEPORT_DIRECTION, + ImmutableMap.of("direction", TextComponent.of("NE").color(TextColor.AQUA))))) + .build(); + Component northCorners = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_NORTH_CORNERS.color(TextColor.YELLOW)) + .append(" : ") + .append(northWestCorner) + .append(northEastCorner).build(); + Component claimAccessors = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_ACCESSORS.color(TextColor.YELLOW)) + .append(" : ") + .append(accessors.equals("") ? NONE : TextComponent.of(accessors, TextColor.BLUE)) + .append(" ") + .append(accessorGroups, TextColor.LIGHT_PURPLE).build(); + Component claimBuilders = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_BUILDERS.color(TextColor.YELLOW)) + .append(" : ") + .append(builders.equals("") ? NONE : TextComponent.of(builders, TextColor.BLUE)) + .append(" ") + .append(builderGroups, TextColor.LIGHT_PURPLE).build(); + Component claimContainers = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_CONTAINERS.color(TextColor.YELLOW)) + .append(" : ") + .append(containers.equals("") ? NONE : TextComponent.of(containers, TextColor.BLUE)) + .append(" ") + .append(containerGroups, TextColor.LIGHT_PURPLE).build(); + Component claimCoowners = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_MANAGERS.color(TextColor.YELLOW)) + .append(" : ") + .append(managers.equals("") ? NONE : TextComponent.of(managers, TextColor.BLUE)) + .append(" ") + .append(managerGroups, TextColor.LIGHT_PURPLE).build(); + Component dateCreated = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_CREATED.color(TextColor.YELLOW)) + .append(" : ") + .append(created != null ? TextComponent.of(created.toString(), TextColor.GRAY) : MessageCache.getInstance().LABEL_UNKNOWN.color(TextColor.GRAY)).build(); + Component dateLastActive = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_LAST_ACTIVE.color(TextColor.YELLOW)) + .append(" : ") + .append(lastActive != null ? TextComponent.of(lastActive.toString(), TextColor.GRAY) : MessageCache.getInstance().LABEL_UNKNOWN.color(TextColor.GRAY)).build(); + + if (claimSpawn != null) { + textList.add(claimSpawn); + } + if (bankInfo != null) { + textList.add(bankInfo); + } + textList.add(claimName); + textList.add(ownerLine); + textList.add(claimTypeInfo); + if (!claim.isAdminClaim() && !claim.isWilderness()) { + textList.add(TextComponent.builder() + .append(claimInherit) + .append(" ") + .append(claimExpired).build()); + if (forSaleText != null) { + textList.add(forSaleText); + } + } + textList.add(TextComponent.builder() + .append(claimPvP) + .append(" ") + .append(claimDenyMessages) + .build()); + textList.add(claimAccessors); + textList.add(claimBuilders); + textList.add(claimContainers); + textList.add(claimCoowners); + textList.add(claimGreeting); + textList.add(claimFarewell); + textList.add(dateCreated); + textList.add(dateLastActive); + textList.add(claimId); + textList.add(northCorners); + textList.add(southCorners); + if (!claim.getParent().isPresent()) { + textList.remove(claimInherit); + } + if (claim.isAdminClaim()) { + textList.remove(bankInfo); + textList.remove(dateLastActive); + } + if (claim.isWilderness()) { + textList.remove(bankInfo); + textList.remove(claimInherit); + textList.remove(claimTypeInfo); + textList.remove(dateLastActive); + textList.remove(northCorners); + textList.remove(southCorners); + } + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(MessageCache.getInstance().CLAIMINFO_UI_TITLE_CLAIMINFO.color(TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(textList); + paginationBuilder.sendTo(src); + } + + public static Consumer<CommandSource> createSettingsConsumer(CommandSource src, Claim claim, List<Component> textList, ClaimType type) { + return settings -> { + Component name = type == ClaimTypes.TOWN ? MessageCache.getInstance().CLAIMINFO_UI_TOWN_SETTINGS : MessageCache.getInstance().CLAIMINFO_UI_ADMIN_SETTINGS; + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(name.color(TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(textList); + paginationBuilder.sendTo(src); + }; + } + + private List<Component> generateAdminSettings(CommandSource src, GDClaim claim) { + List<Component> textList = new ArrayList<>(); + Component returnToClaimInfo = TextComponent.builder() + .append("\n[") + .append(MessageCache.getInstance().CLAIMINFO_UI_RETURN_SETTINGS.color(TextColor.AQUA)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createCommandConsumer(src, "claiminfo", claim.getUniqueId().toString())))).build(); + Component claimResizable = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_RESIZABLE.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, RESIZABLE, claim.getInternalClaimData().isResizable() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component claimRequiresClaimBlocks = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_REQUIRES_CLAIM_BLOCKS.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, REQUIRES_CLAIM_BLOCKS, claim.getInternalClaimData().requiresClaimBlocks() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component claimSizeRestrictions = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_SIZE_RESTRICTIONS.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, SIZE_RESTRICTIONS, claim.getInternalClaimData().hasSizeRestrictions() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component claimExpiration = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_CLAIM_EXPIRATION.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, CLAIM_EXPIRATION, claim.getInternalClaimData().allowExpiration() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + Component claimFlagOverrides = TextComponent.builder() + .append(MessageCache.getInstance().CLAIMINFO_UI_FLAG_OVERRIDES.color(TextColor.YELLOW)) + .append(" : ") + .append(getClickableInfoText(src, claim, FLAG_OVERRIDES, claim.getInternalClaimData().allowFlagOverrides() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED))).build(); + textList.add(returnToClaimInfo); + if (!claim.isAdminClaim() && !claim.isWilderness()) { + textList.add(claimRequiresClaimBlocks); + textList.add(claimExpiration); + textList.add(claimResizable); + textList.add(claimSizeRestrictions); + } + textList.add(claimFlagOverrides); + int fillSize = 20 - (textList.size() + 4); + for (int i = 0; i < fillSize; i++) { + textList.add(TextComponent.of(" ")); + } + return textList; + } + + private void executeAdminSettings(CommandSource src, GDClaim claim) { + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(MessageCache.getInstance().CLAIMINFO_UI_ADMIN_SETTINGS).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(generateAdminSettings(src, claim)); + paginationBuilder.sendTo(src); + } + + public Component getClickableInfoText(CommandSource src, Claim claim, int titleId, Component infoText) { + Component onClickText = MessageCache.getInstance().CLAIMINFO_UI_CLICK_TOGGLE; + boolean hasPermission = true; + if (src instanceof Player) { + Component denyReason = ((GDClaim) claim).allowEdit((Player) src); + if (denyReason != null) { + onClickText = denyReason; + hasPermission = false; + } + } + + TextComponent.Builder textBuilder = TextComponent.builder() + .append(infoText) + .hoverEvent(HoverEvent.showText(onClickText)); + if (hasPermission) { + textBuilder.clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimInfoConsumer(src, claim, titleId)))); + } + return textBuilder.build(); + } + + private Consumer<CommandSource> createClaimInfoConsumer(CommandSource src, Claim claim, int titleId) { + GDClaim gpClaim = (GDClaim) claim; + return info -> { + switch (titleId) { + case INHERIT_PARENT : + if (!claim.getParent().isPresent() || !src.hasPermission(GDPermissions.COMMAND_CLAIM_INHERIT)) { + return; + } + + gpClaim.getInternalClaimData().setInheritParent(!gpClaim.getInternalClaimData().doesInheritParent()); + gpClaim.getInternalClaimData().setRequiresSave(true); + claim.getData().save(); + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + return; + case CLAIM_EXPIRATION : + gpClaim.getInternalClaimData().setExpiration(!gpClaim.getInternalClaimData().allowExpiration()); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + break; + case DENY_MESSAGES : + gpClaim.getInternalClaimData().setDenyMessages(!gpClaim.getInternalClaimData().allowDenyMessages()); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + return; + case FLAG_OVERRIDES : + gpClaim.getInternalClaimData().setFlagOverrides(!gpClaim.getInternalClaimData().allowFlagOverrides()); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + break; + case PVP_OVERRIDE : + Tristate value = gpClaim.getInternalClaimData().getPvpOverride(); + if (value == Tristate.UNDEFINED) { + gpClaim.getInternalClaimData().setPvpOverride(Tristate.TRUE); + } else if (value == Tristate.TRUE) { + gpClaim.getInternalClaimData().setPvpOverride(Tristate.FALSE); + } else { + gpClaim.getInternalClaimData().setPvpOverride(Tristate.UNDEFINED); + } + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + return; + /*case RAID_OVERRIDE : + GDPermissionHolder holder = null; + final GDPlayerData playerData = ((GDClaim) claim).getOwnerPlayerData(); + if (playerData == null) { + holder = GriefDefenderPlugin.DEFAULT_HOLDER; + } else { + holder = playerData.getSubject(); + } + final Boolean result = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Boolean.class), holder, Options.RAID, gpClaim); + final Set<Context> contexts = new HashSet<>(); + contexts.add(claim.getContext()); + if (result) { + PermissionUtil.getInstance().setOptionValue(holder, Options.RAID.getPermission(), "false", contexts); + } else { + PermissionUtil.getInstance().setOptionValue(holder, Options.RAID.getPermission(), "true", contexts); + } + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + return;*/ + case RESIZABLE : + boolean resizable = gpClaim.getInternalClaimData().isResizable(); + gpClaim.getInternalClaimData().setResizable(!resizable); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + break; + case REQUIRES_CLAIM_BLOCKS : + boolean requiresClaimBlocks = gpClaim.getInternalClaimData().requiresClaimBlocks(); + gpClaim.getInternalClaimData().setRequiresClaimBlocks(!requiresClaimBlocks); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + break; + case SIZE_RESTRICTIONS : + boolean sizeRestrictions = gpClaim.getInternalClaimData().hasSizeRestrictions(); + gpClaim.getInternalClaimData().setSizeRestrictions(!sizeRestrictions); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + break; + case FOR_SALE : + boolean forSale = gpClaim.getEconomyData().isForSale(); + gpClaim.getEconomyData().setForSale(!forSale); + gpClaim.getInternalClaimData().setRequiresSave(true); + gpClaim.getClaimStorage().save(); + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + return; + default: + } + executeAdminSettings(src, gpClaim); + }; + } + + private static Consumer<CommandSource> createClaimTypeConsumer(CommandSource src, Claim gpClaim, ClaimType clicked, boolean isAdmin) { + GDClaim claim = (GDClaim) gpClaim; + return type -> { + if (!(src instanceof Player)) { + // ignore + return; + } + + final Player player = (Player) src; + if (!isAdmin && ((GDClaim) gpClaim).allowEdit(player) != null) { + TextAdapter.sendComponent(src, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + final ClaimResult result = claim.changeType(clicked, Optional.of(player.getUniqueId()), src); + if (result.successful()) { + CommandHelper.executeCommand(src, "claiminfo", gpClaim.getUniqueId().toString()); + } else { + TextAdapter.sendComponent(src, result.getMessage().get()); + } + }; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimInherit.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimInherit.java new file mode 100644 index 0000000..2c37418 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimInherit.java @@ -0,0 +1,69 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_INHERIT) +public class CommandClaimInherit extends BaseCommand { + + @CommandAlias("claiminherit") + @Description("Toggles subdivision inherit mode.") + @Subcommand("claim inherit") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component result = claim.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + if (claim.parent == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_INHERIT_ONLY_CHILD); + return; + } + claim.getData().setInheritParent(!claim.getData().doesInheritParent()); + claim.getInternalClaimData().setRequiresSave(true); + + if (!claim.getData().doesInheritParent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CLAIMINHERIT_DISABLED); + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_CLAIMINHERIT_ENABLED); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimList.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimList.java new file mode 100644 index 0000000..a1c8eaa --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimList.java @@ -0,0 +1,246 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PaginationUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.world.World; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_LIST) +public class CommandClaimList extends BaseCommand { + + private final ClaimType forcedType; + private final Cache<UUID, String> lastActiveClaimTypeMap = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + public CommandClaimList() { + this.forcedType = null; + } + + public CommandClaimList(ClaimType type) { + this.forcedType = type; + } + + @CommandCompletion("@gdplayers @gdworlds @gddummy") + @CommandAlias("claimlist|claimslist") + @Syntax("[<player>|<player> <world>]") + @Description("List information about a player's claim blocks and claims.") + @Subcommand("claim list") + public void execute(Player src, @Optional User targetPlayer, @Optional World world) {//String[] args) { + final GDPermissionUser user = targetPlayer == null ? PermissionHolderCache.getInstance().getOrCreateUser(src) : PermissionHolderCache.getInstance().getOrCreateUser(targetPlayer); + if (user == null) { + GriefDefenderPlugin.sendMessage(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", targetPlayer))); + return; + } + if (world == null) { + world = src.getWorld(); + } + + showClaimList(src, user, this.forcedType, world.getUniqueId()); + } + + private void showClaimList(Player src, GDPermissionUser user, ClaimType type, UUID worldUniqueId) { + List<Component> claimsTextList = new ArrayList<>(); + Set<Claim> claims = new HashSet<>(); + final String worldName = worldUniqueId == null ? "" : Sponge.getServer().getWorld(worldUniqueId).get().getName(); + final boolean otherUser = !src.getUniqueId().equals(user.getUniqueId()); + for (World world : Sponge.getServer().getWorlds()) { + if (type != null && !world.getUniqueId().equals(worldUniqueId)) { + continue; + } + final GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + // load the target player's data + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(world, user.getUniqueId()); + Set<Claim> claimList = null; + if (type == null || otherUser) { + claimList = playerData.getClaims(); + } else { + claimList = claimWorldManager.getWorldClaims(); + } + + for (Claim claim : claimList) { + if (claims.contains(claim)) { + continue; + } + + if (((GDClaim) claim).allowEdit(src) != null && !claim.isUserTrusted(src.getUniqueId(), TrustTypes.ACCESSOR)) { + continue; + } + if (type == null) { + claims.add(claim); + } else { + if (claim.getType() == type) { + claims.add(claim); + } else if (type == ClaimTypes.SUBDIVISION) { + for (Claim child : claim.getChildren(true)) { + if (child.getType() == type) { + claims.add(child); + } + } + } + } + } + } + if (src instanceof Player) { + final Player player = (Player) src; + final String lastClaimType = this.lastActiveClaimTypeMap.getIfPresent(player.getUniqueId()); + String currentType = type == null ? "OWN" : type.toString(); + if (lastClaimType != null && !lastClaimType.equals(currentType.toString())) { + PaginationUtil.getInstance().resetActivePage(player.getUniqueId()); + } + } + claimsTextList = CommandHelper.generateClaimTextListCommand(claimsTextList, claims, worldName, user, src, createClaimListConsumer(src, user, type, worldUniqueId), false); + + final Component whiteOpenBracket = TextComponent.of("["); + final Component whiteCloseBracket = TextComponent.of("]"); + Component ownedShowText = MessageCache.getInstance().CLAIMLIST_UI_CLICK_VIEW_CLAIMS; + Component adminShowText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("ADMIN", TextColor.RED))); + Component basicShowText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("BASIC", TextColor.YELLOW))); + Component subdivisionShowText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("SUBDIVISION", TextColor.AQUA))); + Component townShowText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", TextComponent.of("TOWN", TextColor.GREEN))); + Component ownedTypeText = TextComponent.builder("") + .append(type == null ? + TextComponent.builder("") + .append(whiteOpenBracket) + .append(otherUser ? TextComponent.of(user.getFriendlyName()).color(TextColor.GOLD) : MessageCache.getInstance().TITLE_OWN.color(TextColor.GOLD)) + .append(whiteCloseBracket).build() : otherUser ? TextComponent.of(user.getFriendlyName()).color(TextColor.GRAY) : MessageCache.getInstance().TITLE_OWN.color(TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimListConsumer(src, user, null, worldUniqueId)))) + .hoverEvent(HoverEvent.showText(ownedShowText)).build(); + Component adminTypeText = TextComponent.builder("") + .append(type == ClaimTypes.ADMIN ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("ADMIN", TextColor.RED) + .append(whiteCloseBracket).build() : TextComponent.of("ADMIN", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimListConsumer(src, user, ClaimTypes.ADMIN, worldUniqueId)))) + .hoverEvent(HoverEvent.showText(adminShowText)).build(); + Component basicTypeText = TextComponent.builder("") + .append(type == ClaimTypes.BASIC ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("BASIC", TextColor.YELLOW) + .append(whiteCloseBracket).build() : TextComponent.of("BASIC", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimListConsumer(src, user, ClaimTypes.BASIC, worldUniqueId)))) + .hoverEvent(HoverEvent.showText(basicShowText)).build(); + Component subTypeText = TextComponent.builder("") + .append(type == ClaimTypes.SUBDIVISION ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("SUBDIVISION", TextColor.AQUA) + .append(whiteCloseBracket).build() : TextComponent.of("SUBDIVISION", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimListConsumer(src, user, ClaimTypes.SUBDIVISION, worldUniqueId)))) + .hoverEvent(HoverEvent.showText(subdivisionShowText)).build(); + Component townTypeText = TextComponent.builder("") + .append(type == ClaimTypes.TOWN ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("TOWN", TextColor.GREEN) + .append(whiteCloseBracket).build() : TextComponent.of("TOWN", TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createClaimListConsumer(src, user, ClaimTypes.TOWN, worldUniqueId)))) + .hoverEvent(HoverEvent.showText(townShowText)).build(); + Component claimListHead = TextComponent.builder("") + .append(" ") + .append(MessageCache.getInstance().LABEL_DISPLAYING.color(TextColor.AQUA)) + .append(" : ", TextColor.AQUA) + .append(ownedTypeText) + .append(" ") + .append(otherUser ? TextComponent.of("") : adminTypeText) + .append(otherUser ? "" : " ") + .append(basicTypeText) + .append(" ") + .append(subTypeText) + .append(" ") + .append(townTypeText).build(); + final int fillSize = 20 - (claimsTextList.size() + 2); + for (int i = 0; i < fillSize; i++) { + claimsTextList.add(TextComponent.of(" ")); + } + + PaginationList paginationList = PaginationList.builder() + .title(claimListHead).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(claimsTextList).build(); + Integer activePage = 1; + if (src instanceof Player) { + final Player player = (Player) src; + activePage = PaginationUtil.getInstance().getActivePage(player.getUniqueId()); + if (activePage == null) { + activePage = 1; + } + this.lastActiveClaimTypeMap.put(player.getUniqueId(), type == null ? "OWN" : type.toString()); + } + paginationList.sendTo(src, activePage); + } + + private Consumer<CommandSource> createClaimListConsumer(Player src, GDPermissionUser user, ClaimType type, UUID worldUniqueId) { + return consumer -> { + showClaimList(src, user, type, worldUniqueId); + }; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimMode.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimMode.java new file mode 100644 index 0000000..64daa22 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimMode.java @@ -0,0 +1,78 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; + +import org.spongepowered.api.entity.living.player.Player; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_MODE) +public class CommandClaimMode extends BaseCommand { + + @CommandAlias("claim|claimmode") + @Description("Toggles claim mode creation. Note: This will default to basic claim mode.") + @Subcommand("mode claim") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.claimMode = !playerData.claimMode; + playerData.claimSubdividing = null; + if (!playerData.claimMode) { + playerData.revertActiveVisual(player); + // check for any active WECUI visuals + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().revertVisuals(player, playerData, null); + } + TextAdapter.sendComponent(player, MessageCache.getInstance().COMMAND_CLAIMMODE_DISABLED); + } else { + TextAdapter.sendComponent(player, MessageCache.getInstance().COMMAND_CLAIMMODE_ENABLED); + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PLAYER_REMAINING_BLOCKS_3D, + ImmutableMap.of( + "block-amount", playerData.getRemainingClaimBlocks(), + "chunk-amount", playerData.getRemainingChunks())); + GriefDefenderPlugin.sendMessage(player, message); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PLAYER_REMAINING_BLOCKS_2D, + ImmutableMap.of( + "block-amount", playerData.getRemainingClaimBlocks())); + GriefDefenderPlugin.sendMessage(player, message); + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimName.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimName.java new file mode 100644 index 0000000..f3b0ed8 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimName.java @@ -0,0 +1,74 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SET_CLAIM_NAME) +public class CommandClaimName extends BaseCommand { + + @CommandAlias("claimname") + @Syntax("<name>") + @Description("Sets the name of your claim.") + @Subcommand("claim name") + public void execute(Player player, String name) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component result = claim.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + final Component text = LegacyComponentSerializer.legacy().deserialize(name, '&'); + if (text == TextComponent.empty()) { + claim.getInternalClaimData().setName(null); + } else { + claim.getInternalClaimData().setName(text); + } + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_NAME, + ImmutableMap.of( + "name", text)); + GriefDefenderPlugin.sendMessage(player, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimOption.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimOption.java new file mode 100644 index 0000000..cf5cc6b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimOption.java @@ -0,0 +1,85 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.registry.OptionRegistryModule; +import com.griefdefender.util.CauseContextHelper; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_OPTIONS_CLAIM) +public class CommandClaimOption extends ClaimOptionBase { + + public CommandClaimOption() { + super(ClaimSubjectType.GLOBAL); + } + + @CommandCompletion("@gdoptions @gddummy") + @CommandAlias("co|claimoption") + @Description("Gets/Sets claim options in the claim you are standing in.") + @Syntax("[<option> <value> [context[key=value]]") + @Subcommand("option claim") + public void execute(Player player, @Optional String[] args) throws InvalidCommandArgument { + this.subject = GriefDefenderPlugin.DEFAULT_HOLDER; + this.friendlySubjectName = "ALL"; + super.execute(player, args); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionGroup.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionGroup.java new file mode 100644 index 0000000..a4c8832 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionGroup.java @@ -0,0 +1,74 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_OPTIONS_GROUP) +public class CommandClaimOptionGroup extends ClaimOptionBase { + + public CommandClaimOptionGroup() { + super(ClaimSubjectType.GROUP); + } + + @CommandCompletion("@gdgroups @gdoptions @gddummy") + @CommandAlias("cog") + @Description("Gets/Sets option for a group in claim you are standing in.") + @Syntax("<group> <option> <value> [context[key=value]]") + @Subcommand("option group") + public void execute(Player player, String group, @Optional String[] args) throws InvalidCommandArgument { + if (args.length < 2 || args.length > 3) { + throw new InvalidCommandArgument(); + } + + if (!PermissionUtil.getInstance().hasGroupSubject(group)) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_INVALID_GROUP, ImmutableMap.of( + "group", group)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + this.subject = PermissionHolderCache.getInstance().getOrCreateHolder(group); + this.friendlySubjectName = group; + + super.execute(player, args); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionPlayer.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionPlayer.java new file mode 100644 index 0000000..5b42da4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimOptionPlayer.java @@ -0,0 +1,58 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_OPTIONS_PLAYER) +public class CommandClaimOptionPlayer extends ClaimOptionBase { + + public CommandClaimOptionPlayer() { + super(ClaimSubjectType.PLAYER); + } + + @CommandCompletion("@gdplayers @gdoptions @gddummy") + @CommandAlias("cop") + @Description("Gets/Sets option for a player in claim you are standing in.") + @Syntax("<player> <option> <value> [context[key=value]]") + @Subcommand("option player") + public void execute(Player src, User player, @Optional String[] args) throws InvalidCommandArgument { + this.subject = PermissionHolderCache.getInstance().getOrCreateUser(player); + this.friendlySubjectName = player.getName(); + super.execute(src, args); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionGroup.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionGroup.java new file mode 100644 index 0000000..0c8ff67 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionGroup.java @@ -0,0 +1,147 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.Context; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.ui.UIHelper; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_PERMISSION_GROUP) +public class CommandClaimPermissionGroup extends BaseCommand { + + @CommandCompletion("@gdgroups @gddummy") + @CommandAlias("cpg") + @Description("Sets a permission on a group with a claim context.") + @Syntax("<group> [<permission> <value>]") + @Subcommand("permission group") + public void execute(Player player, String group, @Optional String[] args) throws CommandException, InvalidCommandArgument { + String permission = null; + String value = null; + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (args.length > 0) { + if (args.length < 2) { + throw new InvalidCommandArgument(); + } + permission = args[0]; + if (!playerData.ignoreClaims && permission != null && !player.hasPermission(permission)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_ASSIGN_WITHOUT_HAVING); + return; + } + + value = args[1]; + } + + if (!PermissionUtil.getInstance().hasGroupSubject(group)) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_INVALID_GROUP)); + return; + } + + final GDPermissionHolder subj = PermissionHolderCache.getInstance().getOrCreateHolder(group); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_MANAGE, + ImmutableMap.of( + "type", claim.getType().getName())); + if (claim.isWilderness() && !playerData.canManageWilderness) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } else if (claim.isAdminClaim() && !playerData.canManageAdminClaims) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + Set<Context> contexts = new HashSet<>(); + contexts.add(claim.getContext()); + if (permission == null || value == null) { + List<Component> permList = Lists.newArrayList(); + Map<String, Boolean> permissions = PermissionUtil.getInstance().getPermissions(subj, contexts); + for (Map.Entry<String, Boolean> permissionEntry : permissions.entrySet()) { + Boolean permValue = permissionEntry.getValue(); + Component permText = TextComponent.builder("") + .append(permissionEntry.getKey(), TextColor.GREEN) + .append(" ") + .append(permValue.toString(), TextColor.GOLD).build(); + permList.add(permText); + } + + List<Component> finalTexts = UIHelper.stripeText(permList); + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(TextComponent.of(subj.getFriendlyName() + " Permissions", TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(finalTexts); + paginationBuilder.sendTo(player); + return; + } + + Tristate tristateValue = PermissionUtil.getInstance().getTristateFromString(value); + if (tristateValue == null) { + TextAdapter.sendComponent(player, TextComponent.of("Invalid value entered. '" + value + "' is not a valid value. Valid values are : true, false, undefined, 1, -1, or 0.", TextColor.RED)); + return; + } + + PermissionUtil.getInstance().setPermissionValue(subj, permission, tristateValue, contexts); + TextAdapter.sendComponent(player, TextComponent.builder("") + .append("Set permission ") + .append(permission, TextColor.AQUA) + .append(" to ") + .append(value, TextColor.GREEN) + .append(" on group ") + .append(subj.getFriendlyName(), TextColor.GOLD) + .append(".").build()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionPlayer.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionPlayer.java new file mode 100644 index 0000000..d10f950 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimPermissionPlayer.java @@ -0,0 +1,144 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.Context; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.ui.UIHelper; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_PERMISSION_PLAYER) +public class CommandClaimPermissionPlayer extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("cpp") + @Description("Sets a permission on a player with a claim context.") + @Syntax("<player> [<permission> <value>]") + @Subcommand("permission player") + public void execute(Player player, User user, @Optional String[] args) throws CommandException, InvalidCommandArgument { + String permission = null; + String value = null; + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (args.length > 0) { + if (args.length < 2) { + throw new InvalidCommandArgument(); + } + permission = args[0]; + if (!playerData.ignoreClaims && permission != null && !player.hasPermission(permission)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_ASSIGN_WITHOUT_HAVING); + return; + } + + value = args[1]; + } + + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_MANAGE, + ImmutableMap.of( + "type", claim.getType().getName())); + if (claim.isWilderness() && !playerData.canManageWilderness) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } else if (claim.isAdminClaim() && !playerData.canManageAdminClaims) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateUser(player); + Set<Context> contexts = new HashSet<>(); + contexts.add(claim.getContext()); + if (permission == null || value == null) { + // display current permissions for user + List<Component> permList = Lists.newArrayList(); + Map<String, Boolean> permissions = PermissionUtil.getInstance().getPermissions(holder, contexts); + for (Map.Entry<String, Boolean> permissionEntry : permissions.entrySet()) { + Boolean permValue = permissionEntry.getValue(); + Component permText = TextComponent.builder("") + .append(permissionEntry.getKey(), TextColor.GREEN) + .append(" ") + .append(permValue.toString(), TextColor.GOLD).build(); + permList.add(permText); + } + + List<Component> finalTexts = UIHelper.stripeText(permList); + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(TextComponent.of(user.getName() + " Permissions", TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(finalTexts); + paginationBuilder.sendTo(player); + return; + } + + Tristate tristateValue = PermissionUtil.getInstance().getTristateFromString(value); + if (tristateValue == null) { + TextAdapter.sendComponent(player, TextComponent.of("Invalid value entered. '" + value + "' is not a valid value. Valid values are : true, false, undefined, 1, -1, or 0.", TextColor.RED)); + return; + } + + PermissionUtil.getInstance().setPermissionValue(holder, permission, tristateValue, contexts); + TextAdapter.sendComponent(player, TextComponent.builder("") + .append("Set permission ") + .append(permission, TextColor.AQUA) + .append(" to ") + .append(value, TextColor.GREEN) + .append(" on user ") + .append(user.getName(), TextColor.GOLD) + .append(".").build()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSchematic.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSchematic.java new file mode 100644 index 0000000..59f7151 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSchematic.java @@ -0,0 +1,154 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimSchematic; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.sql.Date; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_SCHEMATIC) +public class CommandClaimSchematic extends BaseCommand { + + @CommandAlias("claimschematic") + @Description("Manages claim schematics. Use '/claimschematic create <name>' to create a live backup of claim.") + @Syntax("<create|delete> <name>") + @Subcommand("schematic") + public void execute(Player player, @Optional String[] args) throws CommandException, InvalidCommandArgument { + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() == null) { + TextAdapter.sendComponent(player, MessageCache.getInstance().COMMAND_WORLDEDIT_MISSING); + return; + } + + String action = null; + String name = null; + if (args.length > 0) { + action = args[0]; + if (args.length < 2) { + throw new InvalidCommandArgument(); + } + name = args[1]; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component denyMessage = claim.allowEdit(player); + if (denyMessage != null) { + GriefDefenderPlugin.sendMessage(player, denyMessage); + return; + } + + if (action == null) { + if (claim.schematics.isEmpty()) { + TextAdapter.sendComponent(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SCHEMATIC_NONE)); + return; + } + + List<Component> schematicTextList = new ArrayList<>(); + for (ClaimSchematic schematic : claim.schematics.values()) { + final String schematicName = schematic.getName(); + final Instant schematicDate = schematic.getDateCreated(); + schematicTextList.add( + TextComponent.builder("") + .append(schematicName, TextColor.GREEN) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(displayConfirmationConsumer(player, claim, schematic)))) + .hoverEvent(HoverEvent.showText(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SCHEMATIC_RESTORE_CLICK, + ImmutableMap.of( + "name", TextComponent.of(schematicName, TextColor.GREEN), + "date", TextComponent.of(Date.from(schematicDate).toString(), TextColor.AQUA))))) + .build()); + } + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(TextComponent.of("Schematics", TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(schematicTextList); + paginationBuilder.sendTo(player); + } else if (action.equalsIgnoreCase("create")) { + TextAdapter.sendComponent(player, TextComponent.of("Creating schematic backup...", TextColor.GREEN)); + final ClaimSchematic schematic = ClaimSchematic.builder().claim(claim).name(name).build().orElse(null); + if (schematic != null) { + TextAdapter.sendComponent(player, TextComponent.of("Schematic backup complete.", TextColor.GREEN)); + } else { + TextAdapter.sendComponent(player, TextComponent.of("Schematic could not be created.", TextColor.GREEN)); + } + } else if (action.equalsIgnoreCase("delete")) { + claim.deleteSchematic(name); + TextAdapter.sendComponent(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SCHEMATIC_DELETED, + ImmutableMap.of("name", name))); + } + } + + private static Consumer<CommandSource> displayConfirmationConsumer(CommandSource src, Claim claim, ClaimSchematic schematic) { + return confirm -> { + final Component schematicConfirmationText = TextComponent.builder("") + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(src, claim, schematic)))) + .hoverEvent(HoverEvent.showText(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SCHEMATIC_RESTORE_CONFIRMATION))).build(); + TextAdapter.sendComponent(src, schematicConfirmationText); + }; + } + + private static Consumer<CommandSource> createConfirmationConsumer(CommandSource src, Claim claim, ClaimSchematic schematic) { + return confirm -> { + schematic.apply(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SCHEMATIC_RESTORE_CONFIRMED, + ImmutableMap.of("name", schematic.getName())); + GriefDefenderPlugin.sendMessage(src, message); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSell.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSell.java new file mode 100644 index 0000000..f9ec8a2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSell.java @@ -0,0 +1,129 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_SELL) +public class CommandClaimSell extends BaseCommand { + + @CommandAlias("claimsell") + @Description("Puts your claim up for sale. Use /claimsell amount.\nNote: Requires economy plugin.") + @Syntax("<amount> | cancel") + @Subcommand("sell claim") + public void execute(Player player, String arg) throws InvalidCommandArgument { + if (!GriefDefenderPlugin.getInstance().economyService.isPresent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + + if (claim.isAdminClaim() || claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_CLAIM_NOT_FOR_SALE); + return; + } + + if (!playerData.canIgnoreClaim(claim) && !player.getUniqueId().equals(claim.getOwnerUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_SALE); + return; + } + + Double salePrice = null; + if (!claim.getEconomyData().isForSale()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_CLAIM_NOT_FOR_SALE); + return; + } + if (arg.equalsIgnoreCase("cancel")) { + claim.getEconomyData().setForSale(false); + claim.getEconomyData().setSalePrice(-1); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_CLAIM_SALE_CANCELLED); + return; + } else { + try { + salePrice = Double.parseDouble(arg); + } catch (NumberFormatException e) { + throw new InvalidCommandArgument(); + } + } + + if (salePrice == null || salePrice < 0) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_SALE_INVALID_PRICE, + ImmutableMap.of( + "amount", salePrice)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_SALE_CONFIRMATION, + ImmutableMap.of( + "amount", salePrice)); + GriefDefenderPlugin.sendMessage(player, message); + + final Component saleConfirmationText = TextComponent.builder("") + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createSaleConfirmationConsumer(player, claim, salePrice)))) + .build(); + GriefDefenderPlugin.sendMessage(player, saleConfirmationText); + } + + private static Consumer<CommandSource> createSaleConfirmationConsumer(CommandSource src, Claim claim, double price) { + return confirm -> { + claim.getEconomyData().setSalePrice(price); + claim.getEconomyData().setForSale(true); + claim.getData().save(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_SALE_CONFIRMED, + ImmutableMap.of("amount", price)); + GriefDefenderPlugin.sendMessage(src, message); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSellBlocks.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSellBlocks.java new file mode 100644 index 0000000..6df996d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSellBlocks.java @@ -0,0 +1,154 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; + +import java.math.BigDecimal; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SELL_CLAIM_BLOCKS) +public class CommandClaimSellBlocks extends BaseCommand { + + @CommandAlias("sellclaim|sellclaimblocks|sellblocks") + @Description("Sell your claim blocks for server money.\nNote: Requires economy plugin.") + @Syntax("[<amount>]") + @Subcommand("sell blocks") + public void execute(Player player, @Optional Integer blockCount) { + final boolean economyMode = GriefDefenderPlugin.getInstance().isEconomyModeEnabled(); + // if economy is disabled, don't do anything + if (!GriefDefenderPlugin.getInstance().economyService.isPresent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (playerData.getEconomyClaimBlockReturn() <= 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_BUY_SELL_DISABLED); + return; + } + + // if selling disabled, send error message + if (playerData.getEconomyClaimBlockReturn() == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_ONLY_BUY); + return; + } + + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + int availableBlocks = economyMode ? playerData.getAccruedClaimBlocks() + playerData.getBonusClaimBlocks() : playerData.getInternalRemainingClaimBlocks(); + if (blockCount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_PURCHASE_COST, + ImmutableMap.of( + "amount", playerData.getEconomyClaimBlockReturn(), + "balance", availableBlocks)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } else { + // try to parse number of blocks + if (blockCount <= 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_BUY_INVALID); + return; + } else if (blockCount > availableBlocks) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().ECONOMY_BLOCK_NOT_AVAILABLE); + return; + } + + // attempt to compute value and deposit it + double economyTotalValue = blockCount * playerData.getEconomyClaimBlockReturn(); + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final TransactionResult result = playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(economyTotalValue), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() != ResultType.SUCCESS) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_SELL_ERROR, ImmutableMap.of( + "reason", result.getResult())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + Component message = null; + if (economyMode) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_BLOCK_SALE_CONFIRMATION, + ImmutableMap.of( + "deposit", economyTotalValue, + "balance", String.valueOf("$" + currentFunds), + "amount", playerData.getRemainingClaimBlocks())); + } else { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_BLOCK_SALE_CONFIRMATION, + ImmutableMap.of( + "deposit", economyTotalValue, + "amount", playerData.getRemainingClaimBlocks())); + } + int bonusBlocks = playerData.getBonusClaimBlocks(); + int accruedBlocks = playerData.getAccruedClaimBlocks(); + if (bonusBlocks > 0) { + if (bonusBlocks >= blockCount) { + bonusBlocks = (int) (bonusBlocks - blockCount); + playerData.setBonusClaimBlocks(bonusBlocks); + } else { + int remaining = (int) (blockCount - bonusBlocks); + playerData.setBonusClaimBlocks(0); + playerData.setAccruedClaimBlocks(playerData.getAccruedClaimBlocks() - remaining); + } + } else { + accruedBlocks = (int) (accruedBlocks - blockCount); + playerData.setAccruedClaimBlocks(accruedBlocks); + } + playerData.saveAllData(); + GriefDefenderPlugin.sendMessage(player, message); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSetSpawn.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSetSpawn.java new file mode 100644 index 0000000..78b3a41 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSetSpawn.java @@ -0,0 +1,66 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_SET_SPAWN) +public class CommandClaimSetSpawn extends BaseCommand { + + @CommandAlias("claimsetspawn") + @Description("Sets the spawn of claim.") + @Subcommand("claim setspawn") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Component result = claim.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + final Vector3i pos = VecHelper.toVector3i(player.getLocation()); + claim.getInternalClaimData().setSpawnPos(pos); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SPAWN_SET_SUCCESS, + ImmutableMap.of( + "location", pos)); + GriefDefenderPlugin.sendMessage(player, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSpawn.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSpawn.java new file mode 100644 index 0000000..36a5a8f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSpawn.java @@ -0,0 +1,120 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.serializer.plain.PlainComponentSerializer; + +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_SPAWN) +public class CommandClaimSpawn extends BaseCommand { + + @CommandAlias("claimspawn") + @Description("Teleports you to claim spawn if available.") + @Syntax("[name] [user]") + @Subcommand("claim spawn") + public void execute(Player player, @Optional String claimName, @Optional User targetPlayer) { + final GDPlayerData srcPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDPlayerData targetPlayerData = null; + if (targetPlayer != null) { + targetPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), targetPlayer.getUniqueId()); + } else { + targetPlayerData = srcPlayerData; + } + + GDClaim claim = null; + if (claimName != null) { + for (Claim playerClaim : targetPlayerData.getInternalClaims()) { + String name = null; + Component component = playerClaim.getName().orElse(null); + if (component != null) { + name = PlainComponentSerializer.INSTANCE.serialize(component); + if (claimName.equalsIgnoreCase(name)) { + claim = (GDClaim) playerClaim; + break; + } + } + } + if (claim == null) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_CLAIMNAME_NOT_FOUND, + ImmutableMap.of("name", claimName))); + return; + } + } else { + claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(targetPlayerData, player.getLocation()); + } + + if (!srcPlayerData.canIgnoreClaim(claim) && !claim.isUserTrusted(player, TrustTypes.ACCESSOR) && !player.hasPermission(GDPermissions.COMMAND_DELETE_CLAIMS)) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ACCESS, + ImmutableMap.of("player", claim.getOwnerName()))); + return; + } + + final Vector3i spawnPos = claim.getData().getSpawnPos().orElse(null); + if (spawnPos == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().SPAWN_NOT_SET); + return; + } + + final Location<World> spawnLocation = new Location<>(claim.getWorld(), spawnPos.getX(), spawnPos.getY(), spawnPos.getZ()); + int teleportDelay = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.PLAYER_TELEPORT_DELAY, claim); + if (teleportDelay > 0) { + srcPlayerData.teleportDelay = teleportDelay + 1; + srcPlayerData.teleportLocation = spawnLocation; + return; + } + player.setLocation(spawnLocation); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.SPAWN_TELEPORT, + ImmutableMap.of( + "location", spawnPos)); + GriefDefenderPlugin.sendMessage(player, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimSubdivision.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimSubdivision.java new file mode 100644 index 0000000..ddff33e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimSubdivision.java @@ -0,0 +1,53 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SUBDIVIDE_CLAIMS) +public class CommandClaimSubdivision extends BaseCommand { + + @CommandAlias("modesubdivide|subdivideclaims|sc") + @Description("Switches the shovel tool to subdivision mode, used to subdivide your claims.") + @Subcommand("mode subdivide") + public void execute(Player player) { + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.shovelMode = ShovelTypes.SUBDIVISION; + playerData.claimSubdividing = null; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_SUBDIVISION); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimTown.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimTown.java new file mode 100644 index 0000000..01a1d5d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimTown.java @@ -0,0 +1,53 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TOWN_MODE) +public class CommandClaimTown extends BaseCommand { + + @CommandAlias("modetown") + @Description("Switches the shovel tool to town claims mode.") + @Subcommand("mode town") + public void execute(Player player) { + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.shovelMode = ShovelTypes.TOWN; + playerData.claimSubdividing = null; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_TOWN); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimTransfer.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimTransfer.java new file mode 100644 index 0000000..bdd287c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimTransfer.java @@ -0,0 +1,86 @@ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.data.PlayerData; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; + +import java.util.UUID; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TRANSFER_CLAIM) +public class CommandClaimTransfer extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("claimtransfer|transferclaim") + @Description("Transfers a basic or admin claim to another player.") + @Syntax("<player>") + @Subcommand("claim transfer") + public void execute(Player player, User targetPlayer) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + + if (claim == null || claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_FOUND); + return; + } + + final UUID ownerId = claim.getOwnerUniqueId(); + final boolean isAdmin = playerData.canIgnoreClaim(claim); + // check permission + if (!isAdmin && claim.isAdminClaim() && !player.hasPermission(GDPermissions.COMMAND_ADMIN_CLAIMS)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_TRANSFER_ADMIN); + return; + } else if (!isAdmin && !player.getUniqueId().equals(ownerId) && claim.isUserTrusted(player, TrustTypes.MANAGER)) { + if (claim.parent == null) { + // Managers can only transfer child claims + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + } else if (!isAdmin && !claim.isAdminClaim() && !player.getUniqueId().equals(ownerId)) { + // verify ownership + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + return; + } + + // change ownership + GDCauseStackManager.getInstance().pushCause(player); + final ClaimResult claimResult = claim.transferOwner(targetPlayer.getUniqueId()); + if (!claimResult.successful()) { + PlayerData targetPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), targetPlayer.getUniqueId()); + if (claimResult.getResultType() == ClaimResultType.INSUFFICIENT_CLAIM_BLOCKS) { + TextAdapter.sendComponent(player, TextComponent.of("Could not transfer claim to player with UUID " + targetPlayer.getUniqueId() + "." + + " Player only has " + targetPlayerData.getRemainingClaimBlocks() + " claim blocks remaining." + + " The claim requires a total of " + claim.getClaimBlocks() + " claim blocks to own.", TextColor.RED)); + } else if (claimResult.getResultType() == ClaimResultType.WRONG_CLAIM_TYPE) { + TextAdapter.sendComponent(player, TextComponent.of("The wilderness claim cannot be transferred.", TextColor.RED)); + } else if (claimResult.getResultType() == ClaimResultType.CLAIM_EVENT_CANCELLED) { + TextAdapter.sendComponent(player, TextComponent.of("Could not transfer the claim. A plugin has cancelled the TransferClaimEvent.", TextColor.RED)); + } else { + TextAdapter.sendComponent(player, TextComponent.of("Could not transfer the claim. " + claimResult.getResultType().name(), TextColor.RED)); + } + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_TRANSFER_SUCCESS)); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimUnban.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimUnban.java new file mode 100644 index 0000000..5f1b17e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimUnban.java @@ -0,0 +1,107 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.item.inventory.ItemStack; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.registry.BlockTypeRegistryModule; +import com.griefdefender.internal.registry.EntityTypeRegistryModule; +import com.griefdefender.internal.registry.ItemTypeRegistryModule; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.permission.GDPermissions; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_BAN) +public class CommandClaimUnban extends BaseCommand { + + @CommandCompletion("@gdbantypes @gdmcids @gddummy") + @CommandAlias("claimunban") + @Description("Unbans target id allowing it to be used again.") + @Syntax("hand | <type> <target>") + @Subcommand("unban") + public void execute(Player player, String type, @Optional String id) { + if (type.equalsIgnoreCase("block")) { + if (!BlockTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_BLOCK_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.removeBlockBan(id); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMUNBAN_SUCCESS_BLOCK, + ImmutableMap.of("id", id))); + } else if (type.equalsIgnoreCase("entity")) { + if (!EntityTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_ENTITY_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.removeEntityBan(id); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMUNBAN_SUCCESS_ENTITY, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + } else if (type.equalsIgnoreCase("item")) { + if (!ItemTypeRegistryModule.getInstance().getById(id).isPresent()) { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.REGISTRY_ITEM_NOT_FOUND, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + return; + } + + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.removeItemBan(id); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMUNBAN_SUCCESS_ITEM, + ImmutableMap.of("id", TextComponent.of(id, TextColor.LIGHT_PURPLE)))); + } else if (type.equalsIgnoreCase("hand")) { + final ItemStack itemInHand = player.getItemInHand(HandTypes.MAIN_HAND).orElse(null); + if (itemInHand == null) { + return; + } + final String handItemId = itemInHand.getType().getId(); + GriefDefenderPlugin.getGlobalConfig().getConfig().bans.removeItemBan(handItemId); + GriefDefenderPlugin.getGlobalConfig().save(); + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_CLAIMUNBAN_SUCCESS_ITEM, + ImmutableMap.of("id", TextComponent.of(handItemId, TextColor.LIGHT_PURPLE)))); + } else { + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_TYPE, + ImmutableMap.of("type", type))); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandClaimWorldEdit.java b/sponge/src/main/java/com/griefdefender/command/CommandClaimWorldEdit.java new file mode 100644 index 0000000..a68bf03 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandClaimWorldEdit.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_CLAIM_WORLDEDIT) +public class CommandClaimWorldEdit extends BaseCommand { + + @CommandAlias("claimwe|claimworldedit") + @Description("Uses the worldedit selection to create a claim.") + @Subcommand("claim worldedit|claim we") + public void execute(Player player) { + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_WORLDEDIT_MISSING); + return; + } + + GriefDefenderPlugin.getInstance().getWorldEditProvider().createClaim(player); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandDebug.java b/sponge/src/main/java/com/griefdefender/command/CommandDebug.java new file mode 100644 index 0000000..e25f804 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandDebug.java @@ -0,0 +1,116 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.griefdefender.GDDebugData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.User; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_ADMIN_DEBUG) +public class CommandDebug extends BaseCommand { + + @CommandAlias("gddebug") + @Description("Captures all GD actions for debugging purposes.") + @Syntax("<record|paste|on|off> [filter]") + @Subcommand("debug") + public void execute(CommandSource src, String target, @Optional User user) { + GDDebugData debugData = null; + boolean paste = false; + if (target.equalsIgnoreCase("on")) { + debugData = getOrCreateDebugUser(src, user, true); + } else if (target.equalsIgnoreCase("record")) { + debugData = getOrCreateDebugUser(src, user, false); + } else if (target.equalsIgnoreCase("paste")) { + paste = true; + } else if (target.equalsIgnoreCase("off")) { + GriefDefenderPlugin.getInstance().getDebugUserMap().remove(src.getIdentifier()); + if (GriefDefenderPlugin.getInstance().getDebugUserMap().isEmpty()) { + GriefDefenderPlugin.debugActive = false; + } + } + + if (debugData == null) { + if (paste) { + debugData = GriefDefenderPlugin.getInstance().getDebugUserMap().get(src.getIdentifier()); + if (debugData == null) { + TextAdapter.sendComponent(src, TextComponent.of("Nothing to paste!", TextColor.RED)); + } else { + debugData.pasteRecords(); + } + } + TextAdapter.sendComponent(src, TextComponent.builder("") + .append(GriefDefenderPlugin.GD_TEXT) + .append("Debug ", TextColor.GRAY) + .append("OFF", TextColor.RED) + .build()); + GriefDefenderPlugin.getInstance().getDebugUserMap().remove(src.getIdentifier()); + if (GriefDefenderPlugin.getInstance().getDebugUserMap().isEmpty()) { + GriefDefenderPlugin.debugActive = false; + } + } else { + TextAdapter.sendComponent(src, TextComponent.builder("") + .append(GriefDefenderPlugin.GD_TEXT) + .append("Debug: ", TextColor.GRAY) + .append("ON", TextColor.GREEN) + .append(" | ") + .append("Verbose: ", TextColor.GRAY) + .append(!debugData.isRecording() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED)) + .append(" | ") + .append("Record: ", TextColor.GRAY) + .append(debugData.isRecording() ? TextComponent.of("ON", TextColor.GREEN) : TextComponent.of("OFF", TextColor.RED)) + .append(" | ") + .append("User: ", TextColor.GRAY) + .append(user == null ? "ALL" : user.getName(), TextColor.GOLD) + .build()); + GriefDefenderPlugin.getInstance().getDebugUserMap().put(src.getIdentifier(), debugData); + } + } + + private GDDebugData getOrCreateDebugUser(CommandSource src, User user, boolean verbose) { + GDDebugData debugData = GriefDefenderPlugin.getInstance().getDebugUserMap().get(src.getIdentifier()); + if (debugData == null) { + debugData = new GDDebugData(src, user, verbose); + GriefDefenderPlugin.getInstance().getDebugUserMap().put(src.getIdentifier(), debugData); + } else { + debugData.setTarget(user); + debugData.setVerbose(verbose); + } + GriefDefenderPlugin.debugActive = true; + return debugData; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandException.java b/sponge/src/main/java/com/griefdefender/command/CommandException.java new file mode 100644 index 0000000..aca9170 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandException.java @@ -0,0 +1,92 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import net.kyori.text.Component; + +/** + * Thrown when an executed command raises an error or when execution of + * the command failed. + */ +public class CommandException extends ComponentMessageException { + + private static final long serialVersionUID = 4626722485860074825L; + + private final boolean includeUsage; + + /** + * Constructs a new {@link CommandException} with the given message. + * + * @param message The detail message + */ + public CommandException(Component message) { + this(message, false); + } + + /** + * Constructs a new {@link CommandException} with the given message and + * the given cause. + * + * @param message The detail message + * @param cause The cause + */ + public CommandException(Component message, Throwable cause) { + this(message, cause, false); + } + + /** + * Constructs a new {@link CommandException} with the given message. + * + * @param message The detail message + * @param includeUsage Whether to include usage in the exception + */ + public CommandException(Component message, boolean includeUsage) { + super(message); + this.includeUsage = includeUsage; + } + + /** + * Constructs a new {@link CommandException} with the given message and + * the given cause. + * + * @param message The detail message + * @param cause The cause + * @param includeUsage Whether to include the usage in the exception + */ + public CommandException(Component message, Throwable cause, boolean includeUsage) { + super(message, cause); + this.includeUsage = includeUsage; + } + + /** + * Gets whether the exception should include usage in + * the presentation of the exception/stack-trace. + * + * @return Whether to include usage in the exception + */ + public boolean shouldIncludeUsage() { + return this.includeUsage; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandGDReload.java b/sponge/src/main/java/com/griefdefender/command/CommandGDReload.java new file mode 100644 index 0000000..b0f68c9 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandGDReload.java @@ -0,0 +1,48 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.command.CommandSource; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_RELOAD) +public class CommandGDReload extends BaseCommand { + + @CommandAlias("gdreload") + @Description("Reloads GriefDefender's configuration settings.") + @Subcommand("reload") + public void execute(CommandSource src) { + GriefDefenderPlugin.getInstance().loadConfig(); + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PLUGIN_RELOAD); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandGDVersion.java b/sponge/src/main/java/com/griefdefender/command/CommandGDVersion.java new file mode 100644 index 0000000..1f81d2b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandGDVersion.java @@ -0,0 +1,77 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.service.permission.PermissionService; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_VERSION) +public class CommandGDVersion extends BaseCommand { + + @CommandAlias("gdversion") + @Description("Displays GriefDefender's version information.") + @Subcommand("version") + public void execute(CommandSource src) { + + final String spongePlatform = Sponge.getPlatform().getContainer(org.spongepowered.api.Platform.Component.IMPLEMENTATION).getName(); + Component gpVersion = TextComponent.builder("") + .append(GriefDefenderPlugin.GD_TEXT) + .append("Running ") + .append("GriefDefender " + GriefDefenderPlugin.IMPLEMENTATION_VERSION, TextColor.AQUA) + .build(); + Component spongeVersion = TextComponent.builder("") + .append(GriefDefenderPlugin.GD_TEXT) + .append("Running ") + .append(spongePlatform + " " + GriefDefenderPlugin.SPONGE_VERSION, TextColor.YELLOW) + .build(); + String permissionPlugin = Sponge.getServiceManager().getRegistration(PermissionService.class).get().getPlugin().getId(); + String permissionVersion = Sponge.getServiceManager().getRegistration(PermissionService.class).get().getPlugin().getVersion().orElse("unknown"); + Component permVersion = TextComponent.builder("") + .append(GriefDefenderPlugin.GD_TEXT) + .append("Running ") + .append(permissionPlugin + " " + permissionVersion, TextColor.GREEN) + .build(); + TextAdapter.sendComponent(src, TextComponent.builder("") + .append(gpVersion) + .append("\n") + .append(spongeVersion) + .append("\n") + .append(permVersion) + .build()); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandGiveBlocks.java b/sponge/src/main/java/com/griefdefender/command/CommandGiveBlocks.java new file mode 100644 index 0000000..80e211f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandGiveBlocks.java @@ -0,0 +1,92 @@ +package com.griefdefender.command; + +import java.util.function.Consumer; + +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_GIVE_BLOCKS) +public class CommandGiveBlocks extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("giveblocks") + @Description("Gives claim blocks to another player.") + @Syntax("<player> <amount>") + @Subcommand("giveblocks") + public void execute(Player src, User targetPlayer, int amount) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), src.getUniqueId()); + int availableBlocks = playerData.getAccruedClaimBlocks() + playerData.getBonusClaimBlocks(); + if (amount > availableBlocks) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_GIVEBLOCKS_NOT_ENOUGH, + ImmutableMap.of("amount", TextComponent.of(availableBlocks, TextColor.GOLD)))); + return; + } + + final Component confirmationText = TextComponent.builder() + .append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_GIVEBLOCKS_CONFIRMATION, + ImmutableMap.of("player", TextComponent.of(targetPlayer.getName(), TextColor.AQUA), + "amount", TextComponent.of(amount, TextColor.GREEN)))) + .append(TextComponent.builder() + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(src, targetPlayer, amount)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().UI_CLICK_CONFIRM)).build()) + .build(); + TextAdapter.sendComponent(src, confirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(Player src, User targetPlayer, int amount) { + return confirm -> { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), src.getUniqueId()); + final int accruedTotal = playerData.getAccruedClaimBlocks(); + final int bonusTotal = playerData.getBonusClaimBlocks(); + if (bonusTotal >= amount) { + playerData.setBonusClaimBlocks(bonusTotal - amount); + } else if (accruedTotal >= amount) { + playerData.setAccruedClaimBlocks(accruedTotal- amount); + } else { + int remaining = amount - bonusTotal; + playerData.setBonusClaimBlocks(0); + int newAccrued = accruedTotal - remaining; + playerData.setAccruedClaimBlocks(newAccrued); + } + final GDPlayerData targetPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), targetPlayer.getUniqueId()); + targetPlayerData.setBonusClaimBlocks(targetPlayerData.getBonusClaimBlocks() + amount); + playerData.getStorageData().save(); + targetPlayerData.getStorageData().save(); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_GIVEBLOCKS_CONFIRMED); + TextAdapter.sendComponent(src, message); + + if (targetPlayer.isOnline()) { + final Component targetMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_GIVEBLOCKS_RECEIVED, + ImmutableMap.of("amount", TextComponent.of(amount, TextColor.GOLD), + "player", TextComponent.of(src.getName(), TextColor.AQUA))); + TextAdapter.sendComponent((Player) targetPlayer, targetMessage); + } + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandGivePet.java b/sponge/src/main/java/com/griefdefender/command/CommandGivePet.java new file mode 100644 index 0000000..594be29 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandGivePet.java @@ -0,0 +1,31 @@ +package com.griefdefender.command; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_GIVE_PET) +public class CommandGivePet extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("givepet") + @Description("Transfers a pet to a new owner.") + @Syntax("<player>") + @Subcommand("givepet") + public void execute(Player player, User newOwner) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.petRecipientUniqueId = newOwner.getUniqueId(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_PET_TRANSFER_READY); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandHelp.java b/sponge/src/main/java/com/griefdefender/command/CommandHelp.java new file mode 100644 index 0000000..36973e2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandHelp.java @@ -0,0 +1,51 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.HelpCommand; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandSource; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_HELP) +public class CommandHelp extends BaseCommand { + + @HelpCommand + @Description("Displays GriefDefender command help.") + public void execute(CommandSource src) { + PaginationList.Builder paginationBuilder = + PaginationList.builder().title(TextComponent.of("Showing GriefDefender Help", TextColor.AQUA)).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(GriefDefenderPlugin.helpComponents); + paginationBuilder.sendTo(src); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandHelper.java b/sponge/src/main/java/com/griefdefender/command/CommandHelper.java new file mode 100644 index 0000000..3243de2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandHelper.java @@ -0,0 +1,1352 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Subject; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.economy.BankTransactionType; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.ResultTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.economy.GDBankTransaction; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionResult; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDFlag; +import com.griefdefender.permission.ui.MenuType; +import com.griefdefender.permission.ui.UIHelper; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.text.action.GDCallbackHolder; +import com.griefdefender.util.PermissionUtil; +import com.griefdefender.util.TaskUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.command.CommandException; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.data.property.entity.EyeLocationProperty; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.account.UniqueAccount; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; +import org.spongepowered.api.world.DimensionTypes; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CommandHelper { + + public static Comparator<Component> PLAIN_COMPARATOR = (text1, text2) -> PlainComponentSerializer.INSTANCE.serialize(text1).compareTo(PlainComponentSerializer.INSTANCE.serialize(text2)); + + public static Player checkPlayer(CommandSource source) { + if (source instanceof Player) { + return ((Player) source); + } else { + return null; + } + } + + public static boolean validateFlagTarget(Flag flag, String target) { + if (!(flag instanceof GDFlag)) { + return true; + } + + if (flag.getName().equals("block-break") || flag.getName().equals("block-place") || flag.getName().equals("collide-block")) { + if (validateBlockTarget(target) || + validateItemTarget(target)) { + return true; + } + return false; + } + if (flag.getName().equals("enter-claim") || flag.getName().equals("exit-claim") || flag.getName().equals("entity-riding") || + flag.getName().equals("entity-damage") || flag.getName().equals("portal-use")) { + if (validateEntityTarget(target) || + validateBlockTarget(target) || + validateItemTarget(target)) { + return true; + } + + return false; + } + if (flag.getName().equals("interact-inventory")) { + if (validateEntityTarget(target) || validateBlockTarget(target)) { + return true; + } + + return false; + } + if (flag.getName().equals("liquid-flow") || flag.getName().equals("interact-block-primary") + || flag.getName().equals("interact-block-secondary")) { + return validateBlockTarget(target); + } + if (flag.getName().equals("entity-chunk-spawn") || flag.getName().equals("entity-spawn") || + flag.getName().equals("interact-entity-primary") || flag.getName().equals("interact-entity-secondary")) { + return validateEntityTarget(target); + } + if (flag.getName().equals("item-drop") || flag.getName().equals("item-pickup") || + flag.getName().equals("item-spawn") || flag.getName().equals("item-use")) { + return validateItemTarget(target); + } + + return true; + } + + private static boolean validateEntityTarget(String target) { + Optional<EntityType> entityType = Sponge.getRegistry().getType(EntityType.class, target); + if (entityType.isPresent()) { + return true; + } + + return false; + } + + private static boolean validateItemTarget(String target) { + Optional<ItemType> itemType = Sponge.getRegistry().getType(ItemType.class, target); + if (itemType.isPresent()) { + return true; + } + // target could be an item block, so validate blockstate + Optional<BlockState> blockState = Sponge.getRegistry().getType(BlockState.class, target); + if (blockState.isPresent()) { + return true; + } + + return false; + } + + private static boolean validateBlockTarget(String target) { + Optional<BlockType> blockType = Sponge.getRegistry().getType(BlockType.class, target); + if (blockType.isPresent()) { + return true; + } + + Optional<BlockState> blockState = Sponge.getRegistry().getType(BlockState.class, target); + if (blockState.isPresent()) { + return true; + } + return false; + } + + public static PermissionResult addFlagPermission(CommandSource src, GDPermissionHolder subject, Claim claim, Flag claimFlag, String target, Tristate value, Set<Context> contexts) { + if (src instanceof Player) { + Component denyReason = ((GDClaim) claim).allowEdit((Player) src); + if (denyReason != null) { + GriefDefenderPlugin.sendMessage(src, denyReason); + return new GDPermissionResult(ResultTypes.NO_PERMISSION); + } + } + + final String baseFlag = claimFlag.toString().toLowerCase(); + String flagPermission = GDPermissions.FLAG_BASE + "." + baseFlag; + // special handling for commands + target = adjustTargetForTypes(target, claimFlag); + if (baseFlag.equals(Flags.COMMAND_EXECUTE.getName()) || baseFlag.equals(Flags.COMMAND_EXECUTE_PVP.getName())) { + target = handleCommandFlag(src, target); + if (target == null) { + // failed + return new GDPermissionResult(ResultTypes.TARGET_NOT_VALID); + } + } else { + if (!target.equalsIgnoreCase("any")) { + if (!target.startsWith("#") && !target.contains(":")) { + // assume vanilla + target = "minecraft:" + target; + } + + String[] parts = target.split(":"); + if (parts.length > 1 && parts[1].equalsIgnoreCase("any")) { + target = baseFlag + "." + parts[0]; + } else { + // check for meta + parts = target.split("\\."); + String targetFlag = parts[0]; + if (parts.length > 1) { + try { + Integer.parseInt(parts[1]); + } catch (NumberFormatException e) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_MANAGE, ImmutableMap.of( + "meta", parts[1], + "flag", baseFlag)); + GriefDefenderPlugin.sendMessage(src, message); + return new GDPermissionResult(ResultTypes.TARGET_NOT_VALID); + } + } + addFlagContexts(contexts, claimFlag, targetFlag); + if (!targetFlag.startsWith("#") && !CommandHelper.validateFlagTarget(claimFlag, targetFlag)) { + //TODO + /*final Text message = GriefDefenderPlugin.getInstance().messageData.permissionClaimManage + .apply(ImmutableMap.of( + "target", targetFlag, + "flag", baseFlag)).build();*/ + GriefDefenderPlugin.sendMessage(src,TextComponent.of("Invalid flag " + targetFlag, TextColor.RED)); + return new GDPermissionResult(ResultTypes.TARGET_NOT_VALID); + } + } + } else { + target = ""; + } + } + + return applyFlagPermission(src, subject, claim, flagPermission, target, value, contexts, null, false); + } + + public static PermissionResult applyFlagPermission(CommandSource src, GDPermissionHolder subject, Claim claim, String flagPermission, String target, Tristate value, Set<Context> contexts, MenuType flagType) { + return applyFlagPermission(src, subject, claim, flagPermission, target, value, contexts, flagType, false); + } + + public static PermissionResult applyFlagPermission(CommandSource src, GDPermissionHolder subject, Claim claim, String flagPermission, String target, Tristate value, Set<Context> contexts, MenuType flagType, boolean clicked) { + // Check if player can manage flag + if (src instanceof Player) { + final String basePermission = flagPermission.replace(GDPermissions.FLAG_BASE + ".", ""); + Tristate result = Tristate.fromBoolean(src.hasPermission(GDPermissions.USER_CLAIM_FLAGS + "." + basePermission)); + + if (result != Tristate.TRUE) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PERMISSION_FLAG_USE); + return new GDPermissionResult(ResultTypes.NO_PERMISSION); + } + } + + boolean hasDefaultContext = false; + boolean hasOverrideContext = false; + Component reason = null; + Iterator<Context> iterator = contexts.iterator(); + while (iterator.hasNext()) { + final Context context = iterator.next(); + // validate perms + if (context.getKey().equalsIgnoreCase("reason")) { + reason = LegacyComponentSerializer.legacy().deserialize(context.getValue(), '&'); + iterator.remove(); + continue; + } + if (hasDefaultContext || hasOverrideContext) { + continue; + } + if (context.getKey().contains("gd_claim_default")) { + hasDefaultContext = true; + } else if (context.getKey().contains("gd_claim_override")) { + hasOverrideContext = true; + } + } + // Add target context + if (target != null && !target.isEmpty() && !target.equalsIgnoreCase("any")) { + contexts.add(new Context("target", target)); + } + + if (flagType == null) { + if (hasDefaultContext) { + flagType = MenuType.DEFAULT; + } else if (hasOverrideContext) { + flagType = MenuType.OVERRIDE; + } else { + flagType = MenuType.CLAIM; + } + } + + TextComponent.Builder builder = null; + if (flagType == MenuType.OVERRIDE) { + builder = TextComponent.builder("OVERRIDE").color(TextColor.RED); + } else if (flagType == MenuType.DEFAULT) { + builder = TextComponent.builder("DEFAULT").color(TextColor.LIGHT_PURPLE); + } else if (flagType == MenuType.CLAIM) { + builder = TextComponent.builder("CLAIM").color(TextColor.GOLD); + if (contexts instanceof HashSet) { + contexts.add(claim.getContext()); + } + } + Component flagTypeText = builder.build(); + + if (contexts.isEmpty()) { + // default to claim + contexts.add(claim.getContext()); + } + // wilderness overrides affect all worlds + if (!contexts.contains(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT) && !contexts.contains(claim.getContext())) { + if (contexts instanceof HashSet) { + //contexts.add(claim.getWorld().getContext()); + } + } + + if (subject == GriefDefenderPlugin.DEFAULT_HOLDER) { + PermissionUtil.getInstance().setPermissionValue(GriefDefenderPlugin.DEFAULT_HOLDER, flagPermission, value, contexts); + if (!clicked && src instanceof Player) { + TextAdapter.sendComponent(src, TextComponent.builder("") + .append(TextComponent.builder("\n[").append(MessageCache.getInstance().FLAG_UI_RETURN_FLAGS.color(TextColor.AQUA)).append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createCommandConsumer(src, "claimflag", "")))).build()) + .append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_SET_PERMISSION_TARGET, + ImmutableMap.of( + "type", flagTypeText, + "permission", flagPermission.replace(GDPermissions.FLAG_BASE + ".", ""), + "contexts", getFriendlyContextString(claim, contexts), + "value", getClickableText(src, (GDClaim) claim, subject, contexts, flagPermission, value, flagType).color(TextColor.LIGHT_PURPLE), + "target", "ALL"))) + .build()); + } + } else { + PermissionUtil.getInstance().setPermissionValue(subject, flagPermission, value, contexts); + if (!clicked && src instanceof Player) { + TextAdapter.sendComponent(src, TextComponent.builder("") + .append(TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().FLAG_UI_RETURN_FLAGS.color(TextColor.AQUA)) + .append("]\n", TextColor.WHITE) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createCommandConsumer(src, subject instanceof GDPermissionUser ? "claimflagplayer" : "claimflaggroup", subject.getFriendlyName())))).build()) + .append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_SET_PERMISSION_TARGET, + ImmutableMap.of( + "type", flagTypeText, + "permission", flagPermission.replace(GDPermissions.FLAG_BASE + ".", ""), + "contexts", getFriendlyContextString(claim, contexts), + "value", getClickableText(src, (GDClaim) claim, subject, contexts, flagPermission, value, flagType).color(TextColor.LIGHT_PURPLE), + "target", subject.getFriendlyName()))) + .build()); + } + } + + return new GDPermissionResult(ResultTypes.SUCCESS); + } + + public static String adjustTargetForTypes(String target, Flag flag) { + if (target.equals("player") || target.equals("minecraft:player") || target.equalsIgnoreCase("any")) { + return target; + } + + if (flag.getName().contains("entity") || flag == Flags.ITEM_SPAWN) { + final String contextKey = "target"; + String[] parts = target.split(":"); + String targetId = ""; + if (parts.length == 1) { + targetId = parts[0]; + } else { + targetId = parts[1]; + } + + if (targetId.equalsIgnoreCase("animal")) { + return "#animal"; + } else if (targetId.equalsIgnoreCase("aquatic")) { + return "#aquatic"; + } else if (targetId.equalsIgnoreCase("monster")) { + return "#monster"; + } else if (targetId.equalsIgnoreCase("ambient")) { + return "#ambient"; + } + return target; + } else { + if ((target.equals("food") || target.endsWith(":food")) && !target.startsWith("#")) { + target = "#" + target; + } + } + return target; + } + + public static void addFlagContexts(Set<Context> contexts, Flag flag, String target) { + if (target.equals("player") || target.equals("minecraft:player") || target.equalsIgnoreCase("any")) { + return; + } + + if (flag.getName().contains("entity") || flag == Flags.ITEM_SPAWN) { + final String contextKey = "target"; + String[] parts = target.split(":"); + if (parts.length == 1) { + contexts.add(new Context(contextKey, target)); + return; + } + + if (parts[1].equalsIgnoreCase("animal")) { + contexts.add(new Context(contextKey, "#animal")); + } else if (parts[1].equalsIgnoreCase("aquatic")) { + contexts.add(new Context(contextKey, "#aquatic")); + } else if (parts[1].equalsIgnoreCase("monster")) { + contexts.add(new Context(contextKey, "#monster")); + } else if (parts[1].equalsIgnoreCase("ambient")) { + contexts.add(new Context(contextKey, "#ambient")); + } else { + contexts.add(new Context(contextKey, target)); + } + } + } + + public static Component getFriendlyContextString(Claim claim, Set<Context> contexts) { + if (contexts.isEmpty()) { + return TextComponent.of("[]", TextColor.WHITE); + } + + TextComponent.Builder builder = TextComponent.builder(); + final Iterator<Context> iterator = contexts.iterator(); + while (iterator.hasNext()) { + final Context context = iterator.next(); + builder.append("\n[", TextColor.WHITE) + .append(context.getKey(), TextColor.GREEN) + .append("=", TextColor.GRAY) + .append(context.getValue(), TextColor.WHITE); + + if (iterator.hasNext()) { + builder.append("], "); + } else { + builder.append("]"); + } + } + return builder.build(); + } + + public static TextColor getPermissionMenuTypeColor(MenuType type) { + TextColor color = TextColor.LIGHT_PURPLE; + if (type == MenuType.CLAIM) { + color = TextColor.GOLD; + } else if (type == MenuType.OVERRIDE) { + color = TextColor.RED; + } + + return color; + } + + public static Consumer<CommandSource> createFlagConsumer(CommandSource src, GDClaim claim, Subject subject, Set<Context> contexts, String flagPermission, Tristate flagValue, MenuType flagType) { + return consumer -> { + Tristate newValue = Tristate.UNDEFINED; + if (flagValue == Tristate.TRUE) { + newValue = Tristate.FALSE; + } else if (flagValue == Tristate.UNDEFINED) { + newValue = Tristate.TRUE; + } + + Component flagTypeText = TextComponent.empty(); + if (flagType == MenuType.OVERRIDE) { + flagTypeText = TextComponent.of("OVERRIDE", TextColor.RED); + } else if (flagType == MenuType.DEFAULT) { + flagTypeText = TextComponent.of("DEFAULT", TextColor.LIGHT_PURPLE); + } else if (flagType == MenuType.CLAIM) { + flagTypeText = TextComponent.of("CLAIM", TextColor.GOLD); + } + String target = flagPermission.replace(GDPermissions.FLAG_BASE + ".", ""); + Set<Context> newContexts = new HashSet<>(contexts); + PermissionUtil.getInstance().setPermissionValue(GriefDefenderPlugin.DEFAULT_HOLDER, flagPermission, newValue, newContexts); + TextAdapter.sendComponent(src, TextComponent.builder("") + .append("Set ", TextColor.GREEN) + .append(flagTypeText) + .append(" permission ") + .append(target, TextColor.AQUA) + .append("\n to ", TextColor.GREEN) + .append(getClickableText(src, (GDClaim) claim, subject, newContexts, flagPermission, newValue, flagType).color(TextColor.LIGHT_PURPLE)) + .append(" for ", TextColor.GREEN) + .append(subject.getFriendlyName(), TextColor.GOLD).build()); + }; + } + + public static Consumer<CommandSource> createCommandConsumer(CommandSource src, String command, String arguments) { + return createCommandConsumer(src, command, arguments, null); + } + + public static Consumer<CommandSource> createCommandConsumer(CommandSource src, String command, String arguments, Consumer<CommandSource> postConsumerTask) { + return consumer -> { + try { + Sponge.getCommandManager().get(command).get().getCallable().process(src, arguments); + } catch (CommandException e) { + src.sendMessage(e.getText()); + } + if (postConsumerTask != null) { + postConsumerTask.accept(src); + } + }; + } + + public static void executeCommand(CommandSource src, String command, String arguments) { + try { + Sponge.getCommandManager().get(command).get().getCallable().process(src, arguments); + } catch (CommandException e) { + src.sendMessage(e.getText()); + } + } + + public static void showClaims(CommandSource src, Set<Claim> claims) { + if (claims.isEmpty()) { + // do nothing + return; + } + showClaims(src, claims, 0, false); + } + + public static void showOverlapClaims(CommandSource src, Set<Claim> claims, int height) { + showClaims(src, claims, height, true, true); + } + + public static void showClaims(CommandSource src, Set<Claim> claims, int height, boolean visualizeClaims) { + showClaims(src, claims, height, visualizeClaims, false); + } + + public static void showClaims(CommandSource src, Set<Claim> claims, int height, boolean visualizeClaims, boolean overlap) { + final String worldName = src instanceof Player ? ((Player) src).getWorld().getName() : Sponge.getServer().getDefaultWorldName(); + final boolean canListOthers = src.hasPermission(GDPermissions.LIST_OTHER_CLAIMS); + List<Component> claimsTextList = generateClaimTextList(new ArrayList<Component>(), claims, worldName, null, src, createShowClaimsConsumer(src, claims, height, visualizeClaims), canListOthers, false, overlap); + + if (visualizeClaims && src instanceof Player) { + Player player = (Player) src; + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (claims.size() > 1) { + if (height != 0) { + height = playerData.lastValidInspectLocation != null ? playerData.lastValidInspectLocation.getBlockY() : player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(); + } + ClaimVisual visualization = ClaimVisual.fromClaims(claims, playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? height : player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(), player.getLocation(), playerData, null); + visualization.apply(player); + } else { + for (Claim claim : claims) { + GDClaim gpClaim = (GDClaim) claim; + gpClaim.getVisualizer().createClaimBlockVisuals(height, player.getLocation(), playerData); + gpClaim.getVisualizer().apply(player); + } + } + } + + PaginationList.Builder builder = PaginationList.builder().title(MessageCache.getInstance().CLAIMLIST_UI_TITLE.color(TextColor.RED)).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(claimsTextList); + builder.sendTo(src); + } + + private static Consumer<CommandSource> createShowClaimsConsumer(CommandSource src, Set<Claim> claims, int height, boolean visualizeClaims) { + return consumer -> { + showClaims(src, claims, height, visualizeClaims); + }; + } + + public static List<Component> generateClaimTextListCommand(List<Component> claimsTextList, Set<Claim> claimList, String worldName, GDPermissionUser user, CommandSource src, Consumer<CommandSource> returnCommand, boolean listChildren) { + return generateClaimTextList(claimsTextList, claimList, worldName, user, src, returnCommand, listChildren, false, true); + } + + public static List<Component> generateClaimTextList(List<Component> claimsTextList, Set<Claim> claimList, String worldName, GDPermissionUser user, CommandSource src, Consumer<CommandSource> returnCommand, boolean listChildren) { + return generateClaimTextList(claimsTextList, claimList, worldName, user, src, returnCommand, listChildren, false, false); + } + + public static List<Component> generateClaimTextList(List<Component> claimsTextList, Set<Claim> claimList, String worldName, GDPermissionUser user, CommandSource src, Consumer<CommandSource> returnCommand, boolean listChildren, boolean overlap, boolean listCommand) { + if (claimList.size() > 0) { + for (Claim playerClaim : claimList) { + GDClaim claim = (GDClaim) playerClaim; + if (!listCommand && !overlap && !listChildren && claim.isSubdivision() && !claim.getData().getEconomyData().isForSale()) { + continue; + } + + double teleportHeight = claim.getOwnerPlayerData() == null ? 65.0D : (claim.getOwnerMinClaimLevel() > 65.0D ? claim.getOwnerMinClaimLevel() : 65); + Vector3i lesserPos = claim.lesserBoundaryCorner; + Vector3i greaterPos = claim.greaterBoundaryCorner; + Vector3i center = claim.lesserBoundaryCorner.add(lesserPos.getX(), lesserPos.getY(), lesserPos.getZ()).div(2); + Vector3i newCenter = new Vector3i(center.getX(), teleportHeight, center.getZ()); + Vector3i southWest = new Vector3i(newCenter.getX(), newCenter.getY(), newCenter.getZ()); + //final double teleportHeight = claim.getOwnerPlayerData() == null ? 65.0D : (claim.getOwnerPlayerData().getMinClaimLevel() > 65.0D ? claim.getOwnerPlayerData().getMinClaimLevel() : 65); + //Location<World> southWest = claim.lesserBoundaryCorner.setPosition(new Vector3d(claim.lesserBoundaryCorner.getPosition().getX(), teleportHeight, claim.greaterBoundaryCorner.getPosition().getZ())); + Component claimName = claim.getData().getName().orElse(TextComponent.empty()); + Component teleportName = claim.getData().getName().orElse(claim.getFriendlyNameType()); + Component ownerLine = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_OWNER.color(TextColor.YELLOW)) + .append(" : ", TextColor.WHITE) + .append(claim.getOwnerName().color(TextColor.GOLD)) + .append("\n").build(); + Component claimTypeInfo = TextComponent.builder("Type").color(TextColor.YELLOW) + .append(" : ", TextColor.WHITE) + .append(claim.getFriendlyNameType()) + .append(" ") + .append(claim.isCuboid() ? "3D " : "2D ", TextColor.GRAY) + .append(" (") + .append(MessageCache.getInstance().LABEL_AREA) + .append(": ", TextColor.WHITE) + .append(String.valueOf(claim.getClaimBlocks()), TextColor.GRAY) + .append(" blocks)\n", TextColor.WHITE).build(); + Component clickInfo = MessageCache.getInstance().CLAIMLIST_UI_CLICK_INFO; + Component basicInfo = TextComponent.builder("") + .append(ownerLine) + .append(claimTypeInfo) + .append(clickInfo).build(); + + Component claimInfoCommandClick = TextComponent.builder("") + .append(claim.getFriendlyNameType()) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createCommandConsumer(src, "claiminfo", claim.getUniqueId().toString(), createReturnClaimListConsumer(src, returnCommand))))) + .hoverEvent(HoverEvent.showText(basicInfo)).build(); + + Component claimCoordsTPClick = TextComponent.builder("") + .append("[") + .append("TP", TextColor.LIGHT_PURPLE) + .append("]") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(src, VecHelper.toLocation(claim.getWorld(), southWest), claim)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMLIST_UI_CLICK_TELEPORT_TARGET, + ImmutableMap.of( + "name", teleportName, + "target", southWest.toString(), + "world", claim.getWorld().getName())))) + .build(); + + Component claimSpawn = null; + if (claim.getData().getSpawnPos().isPresent()) { + Vector3i spawnPos = claim.getData().getSpawnPos().get(); + Location<World> spawnLoc = new Location<>(claim.getWorld(), spawnPos.getX(), spawnPos.getY(), spawnPos.getZ()); + claimSpawn = TextComponent.builder("") + .append("[") + .append("TP", TextColor.LIGHT_PURPLE) + .append("]") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createTeleportConsumer(src, spawnLoc, claim, true)))) + .hoverEvent(HoverEvent.showText(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMLIST_UI_CLICK_TELEPORT_TARGET, + ImmutableMap.of( + "name", teleportName, + "target", "'s spawn @ " + spawnPos.toString(), + "world", claim.getWorld().getName())))) + .build(); + } else { + claimSpawn = claimCoordsTPClick; + } + + List<Component> childrenTextList = new ArrayList<>(); + if (!listChildren) { + childrenTextList = generateClaimTextList(new ArrayList<Component>(), claim.getChildren(true), worldName, user, src, returnCommand, true); + } + final Player player = src instanceof Player ? (Player) src : null; + Component buyClaim = TextComponent.empty(); + if (player != null && claim.getEconomyData().isForSale() && claim.getEconomyData().getSalePrice() > -1) { + Component buyInfo = TextComponent.builder() + .append(MessageCache.getInstance().LABEL_PRICE.color(TextColor.AQUA)) + .append(" : ", TextColor.WHITE) + .append(String.valueOf(claim.getEconomyData().getSalePrice()), TextColor.GOLD) + .append("\n") + .append(MessageCache.getInstance().CLAIMLIST_UI_CLICK_PURCHASE).build(); + buyClaim = TextComponent.builder() + .append(claim.getEconomyData().isForSale() ? TextComponent.builder(" [").append(MessageCache.getInstance().LABEL_BUY.color(TextColor.GREEN)).append("]", TextColor.WHITE).build() : TextComponent.empty()) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(buyClaimConsumerConfirmation(src, claim)))) + .hoverEvent(HoverEvent.showText(player.getUniqueId().equals(claim.getOwnerUniqueId()) ? MessageCache.getInstance().CLAIM_OWNER_ALREADY : buyInfo)).build(); + } + if (!childrenTextList.isEmpty()) { + Component children = TextComponent.builder("[") + .append(MessageCache.getInstance().LABEL_CHILDREN.color(TextColor.AQUA)) + .append("]", TextColor.WHITE) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(showChildrenList(childrenTextList, src, returnCommand, claim)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().CLAIMLIST_UI_CLICK_VIEW_CHILDREN)).build(); + claimsTextList.add(TextComponent.builder("") + .append(claimSpawn) + .append(" ") + .append(claimInfoCommandClick) + .append(" : ", TextColor.WHITE) + .append(claim.getOwnerName().color(TextColor.GOLD)) + .append(" ") + .append(claimName == TextComponent.empty() ? TextComponent.of("") : claimName) + .append(" ") + .append(children) + .append(" ") + .append(buyClaim) + .build()); + } else { + claimsTextList.add(TextComponent.builder("") + .append(claimSpawn) + .append(" ") + .append(claimInfoCommandClick) + .append(" : ", TextColor.WHITE) + .append(claim.getOwnerName().color(TextColor.GOLD)) + .append(" ") + .append(claimName == TextComponent.empty() ? TextComponent.of("") : claimName) + .append(buyClaim) + .build()); + } + } + if (claimsTextList.size() == 0) { + claimsTextList.add(MessageCache.getInstance().CLAIMLIST_UI_NO_CLAIMS_FOUND.color(TextColor.RED)); + } + } + return claimsTextList; + } + + public static Consumer<CommandSource> buyClaimConsumerConfirmation(CommandSource src, Claim claim) { + return confirm -> { + final Player player = (Player) src; + if (player.getUniqueId().equals(claim.getOwnerUniqueId())) { + return; + } + Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final double balance = playerAccount.getBalance(GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency()).doubleValue(); + if (balance < claim.getEconomyData().getSalePrice()) { + Map<String, Object> params = ImmutableMap.of( + "amount", claim.getEconomyData().getSalePrice(), + "balance", balance, + "amount_required", claim.getEconomyData().getSalePrice() - balance); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_NOT_ENOUGH_FUNDS, params)); + return; + } + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_CONFIRMATION, + ImmutableMap.of("amount", "$" + claim.getEconomyData().getSalePrice())); + final Component buyConfirmationText = TextComponent.builder() + .append(message) + .append(TextComponent.builder() + .append("\n[") + .append(MessageCache.getInstance().LABEL_CONFIRM.color(TextColor.GREEN)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createBuyConsumerConfirmed(src, claim)))).build()) + .build(); + GriefDefenderPlugin.sendMessage(player, buyConfirmationText); + }; + } + + private static Consumer<CommandSource> createBuyConsumerConfirmed(CommandSource src, Claim claim) { + return confirm -> { + final Player player = (Player) src; + final GDPermissionUser owner = PermissionHolderCache.getInstance().getOrCreateUser(claim.getOwnerUniqueId()); + final Account ownerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(claim.getOwnerUniqueId()).orElse(null); + if (ownerAccount == null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_PLAYER_NOT_FOUND, ImmutableMap.of( + "player", player.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + final ClaimResult result = claim.transferOwner(player.getUniqueId()); + if (!result.successful()) { + final Component defaultMessage = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_TRANSFER_CANCELLED, + ImmutableMap.of( + "owner", owner.getName(), + "player", player.getName())); + TextAdapter.sendComponent(src, result.getMessage().orElse(defaultMessage)); + return; + } + + try (CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final double salePrice = claim.getEconomyData().getSalePrice(); + Sponge.getCauseStackManager().pushCause(src); + final TransactionResult ownerResult = ownerAccount.deposit(defaultCurrency, BigDecimal.valueOf(salePrice), Sponge.getCauseStackManager().getCurrentCause()); + Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + final TransactionResult + transactionResult = + playerAccount.withdraw(defaultCurrency, BigDecimal.valueOf(salePrice), Sponge.getCauseStackManager().getCurrentCause()); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_CONFIRMED, + ImmutableMap.of( + "amount", String.valueOf("$" +salePrice))); + final Component saleMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_SOLD, + ImmutableMap.of( + "amount", String.valueOf("$" +salePrice), + "balance",String.valueOf("$" + playerAccount.getBalance(defaultCurrency)))); + if (owner.getOnlinePlayer() != null) { + TextAdapter.sendComponent(owner.getOnlinePlayer(), saleMessage); + } + claim.getEconomyData().setForSale(false); + claim.getEconomyData().setSalePrice(0); + claim.getData().save(); + GriefDefenderPlugin.sendMessage(src, message); + } + }; + } + + public static Consumer<CommandSource> showChildrenList(List<Component> childrenTextList, CommandSource src, Consumer<CommandSource> returnCommand, GDClaim parent) { + return consumer -> { + Component claimListReturnCommand = TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMLIST_UI_RETURN_CLAIMSLIST.color(TextColor.AQUA)) + .append("]\n", TextColor.WHITE) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(returnCommand))).build(); + + List<Component> textList = new ArrayList<>(); + textList.add(claimListReturnCommand); + textList.addAll(childrenTextList); + PaginationList.Builder builder = PaginationList.builder() + .title(parent.getName().orElse(parent.getFriendlyNameType()) + .append(TextComponent.of(" ").append(MessageCache.getInstance().CLAIMLIST_UI_TITLE_CHILD_CLAIMS))).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList); + builder.sendTo(src); + }; + } + + public static Consumer<CommandSource> createReturnClaimListConsumer(CommandSource src, Consumer<CommandSource> returnCommand) { + return consumer -> { + Component claimListReturnCommand = TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMLIST_UI_RETURN_CLAIMSLIST.color(TextColor.AQUA)) + .append("]\n", TextColor.WHITE) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(returnCommand))).build(); + TextAdapter.sendComponent(src, claimListReturnCommand); + }; + } + + public static Consumer<CommandSource> createReturnClaimListConsumer(CommandSource src, String arguments) { + return consumer -> { + Component claimListReturnCommand = TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMLIST_UI_RETURN_CLAIMSLIST.color(TextColor.AQUA)) + .append("]\n", TextColor.WHITE) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createCommandConsumer(src, "/claimslist", arguments)))).build(); + TextAdapter.sendComponent(src, claimListReturnCommand); + }; + } + + public static Consumer<CommandSource> createFlagConsumer(CommandSource src, GDPermissionHolder subject, String subjectName, Set<Context> contexts, GDClaim claim, String flagPermission, Tristate flagValue, String source) { + return consumer -> { + String target = flagPermission.replace(GDPermissions.FLAG_BASE + ".", ""); + if (target.isEmpty()) { + target = "any"; + } + Tristate newValue = Tristate.UNDEFINED; + if (flagValue == Tristate.TRUE) { + newValue = Tristate.FALSE; + } else if (flagValue == Tristate.UNDEFINED) { + newValue = Tristate.TRUE; + } + + CommandHelper.applyFlagPermission(src, subject, claim, flagPermission, target, newValue, null, MenuType.GROUP); + }; + } + + public static Component getClickableText(CommandSource src, GDClaim claim, Subject subject, Set<Context> contexts, String flagPermission, Tristate flagValue, MenuType type) { + TextComponent.Builder textBuilder = TextComponent.builder(flagValue.toString().toLowerCase()) + .hoverEvent(HoverEvent.showText(TextComponent.builder("") + .append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMLIST_UI_CLICK_TOGGLE_VALUE, + ImmutableMap.of("type", type.name().toLowerCase()))) + .append("\n") + .append(UIHelper.getPermissionMenuTypeHoverText(type)).build())) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createFlagConsumer(src, claim, subject, contexts, flagPermission, flagValue, type)))); + return textBuilder.build(); + } + + public static Component getClickableText(CommandSource src, GDPermissionHolder subject, String subjectName, Set<Context> contexts, GDClaim claim, String flagPermission, Tristate flagValue, String source, MenuType type) { + Component onClickText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.CLAIMLIST_UI_CLICK_TOGGLE_VALUE, + ImmutableMap.of("type", "flag")); + boolean hasPermission = true; + if (type == MenuType.INHERIT) { + onClickText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_UI_INHERIT_PARENT, + ImmutableMap.of("name", claim.getFriendlyNameType())); + hasPermission = false; + } else if (src instanceof Player) { + Component denyReason = claim.allowEdit((Player) src); + if (denyReason != null) { + onClickText = denyReason; + hasPermission = false; + } + } + + TextComponent.Builder textBuilder = TextComponent.builder(flagValue.toString().toLowerCase()) + .hoverEvent(HoverEvent.showText(TextComponent.builder("") + .append(onClickText) + .append("\n") + .append(UIHelper.getPermissionMenuTypeHoverText(type)).build())); + if (hasPermission) { + textBuilder.clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createFlagConsumer(src, subject, subjectName, contexts, claim, flagPermission, flagValue, source)))); + } + return textBuilder.build(); + } + + public static String handleCommandFlag(CommandSource src, String target) { + String pluginId = "minecraft"; + String args = ""; + String command = ""; + int argsIndex = target.indexOf("["); + if (argsIndex != -1) { + if (argsIndex == 0) { + // invalid + TextAdapter.sendComponent(src, MessageCache.getInstance().COMMAND_INVALID); + return null; + } + command = target.substring(0, argsIndex); + String[] parts = command.split(":"); + if (parts.length > 1) { + pluginId = parts[0]; + command = parts[1]; + } + if (!validateCommandMapping(src, command, pluginId)) { + return null; + } + if (!pluginId.equals("minecraft")) { + PluginContainer pluginContainer = Sponge.getPluginManager().getPlugin(pluginId).orElse(null); + if (pluginContainer == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLUGIN_NOT_FOUND, + ImmutableMap.of("id", pluginId))); + return null; + } + } + args = target.substring(argsIndex, target.length()); + Pattern p = Pattern.compile("\\[([^\\]]+)\\]"); + Matcher m = p.matcher(args); + if (!m.find()) { + // invalid + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_EXECUTE_FAILED, + ImmutableMap.of( + "command", command, + "args", args))); + return null; + } + args = m.group(1); + target = pluginId + ":" + command + "." + args.replace(":", "."); + } else { + String[] parts = target.split(":"); + if (parts.length > 1) { + pluginId = parts[0]; + command = parts[1]; + } else { + command = target; + } + target = pluginId + ":" + command; + } + + // validate command + if (!validateCommandMapping(src, command, pluginId)) { + return null; + } + + return target; + } + + private static boolean validateCommandMapping(CommandSource src, String command, String pluginId) { + CommandMapping commandMapping = Sponge.getCommandManager().get(command).orElse(null); + if (commandMapping == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLUGIN_COMMAND_NOT_FOUND, + ImmutableMap.of( + "command", command, + "id", pluginId))); + return false; + } + return true; + } + + public static String getTrustPermission(TrustType trustType) { + if (trustType == TrustTypes.ACCESSOR) { + return GDPermissions.TRUST_ACCESSOR; + } else if (trustType == TrustTypes.CONTAINER) { + return GDPermissions.TRUST_CONTAINER; + } else if (trustType == TrustTypes.BUILDER) { + return GDPermissions.TRUST_BUILDER; + } else { + return GDPermissions.TRUST_MANAGER; + } + } + + public static Consumer<CommandSource> createTeleportConsumer(CommandSource src, Location<World> location, Claim claim) { + return createTeleportConsumer(src, location, claim, false); + } + + public static Consumer<CommandSource> createTeleportConsumer(CommandSource src, Location<World> location, Claim claim, boolean isClaimSpawn) { + return teleport -> { + if (!(src instanceof Player)) { + // ignore + return; + } + Player player = (Player) src; + GDClaim gpClaim = (GDClaim) claim; + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(player.getWorld(), player.getUniqueId()); + if (!playerData.canIgnoreClaim(gpClaim) && !playerData.canManageAdminClaims) { + // if not owner of claim, validate perms + if (!player.getUniqueId().equals(claim.getOwnerUniqueId())) { + if (!player.hasPermission(GDPermissions.COMMAND_CLAIM_INFO_TELEPORT_OTHERS) && !gpClaim.isUserTrusted(player, TrustTypes.ACCESSOR)) { + TextAdapter.sendComponent(player, MessageCache.getInstance().CLAIMINFO_UI_TELEPORT_FEATURE.color(TextColor.RED)); + return; + } + } else if (!player.hasPermission(GDPermissions.COMMAND_CLAIM_INFO_TELEPORT_BASE)) { + TextAdapter.sendComponent(player, MessageCache.getInstance().CLAIMINFO_UI_TELEPORT_FEATURE.color(TextColor.RED)); + return; + } + } + + final int teleportDelay = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.PLAYER_TELEPORT_DELAY, claim); + if (isClaimSpawn) { + if (teleportDelay > 0) { + playerData.teleportDelay = teleportDelay + 1; + playerData.teleportSourceLocation = player.getLocation(); + playerData.teleportLocation = location; + return; + } + + player.setLocation(location); + return; + } + + Location<World> safeLocation = Sponge.getGame().getTeleportHelper().getSafeLocation(location, 64, 16).orElse(null); + if (safeLocation == null) { + TextAdapter.sendComponent(player, TextComponent.builder("") + .append("Location is not safe. ", TextColor.RED) + .append(TextComponent.builder("") + .append("Are you sure you want to teleport here?", TextColor.GREEN) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createForceTeleportConsumer(player, location)))).decoration(TextDecoration.UNDERLINED, true).build()).build()); + } else { + player.setLocation(safeLocation); + } + + // TextAdapter.sendComponent(player, MessageCache.getInstance().TELEPORT_NO_SAFE_LOCATION); + }; + } + + public static Consumer<CommandSource> createForceTeleportConsumer(Player player, Location<World> location) { + return teleport -> { + player.setLocation(location); + }; + } + + public static void handleBankTransaction(CommandSource src, String[] args, GDClaim claim) { + final EconomyService economyService = GriefDefenderPlugin.getInstance().economyService.orElse(null); + if (economyService == null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + if (claim.isSubdivision() || claim.isAdminClaim()) { + return; + } + + Account bankAccount = null;//claim.getEconomyAccount().orElse(null); + if (bankAccount == null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().ECONOMY_VIRTUAL_NOT_SUPPORTED); + return; + } + + final String command = args[0]; + double amount = 0; + try { + amount = Double.valueOf(args[1]); + } catch (NumberFormatException e) { + + } + + final UUID playerSource = ((Player) src).getUniqueId(); + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(claim.getWorld(), claim.getOwnerUniqueId()); + if (playerData.canIgnoreClaim(claim) || claim.getOwnerUniqueId().equals(playerSource) || claim.getUserTrusts(TrustTypes.MANAGER).contains(playerData.playerID)) { + final UniqueAccount playerAccount = economyService.getOrCreateAccount(playerData.playerID).get(); + try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + Sponge.getCauseStackManager().pushCause(src); + Sponge.getCauseStackManager().addContext(GriefDefenderPlugin.PLUGIN_CONTEXT, GriefDefenderPlugin.getInstance()); + if (command.equalsIgnoreCase("withdraw")) { + TransactionResult + result = + bankAccount.withdraw(economyService.getDefaultCurrency(), BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() == ResultType.SUCCESS) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_WITHDRAW, + ImmutableMap.of( + "amount", amount)); + GriefDefenderPlugin.sendMessage(src, message); + playerAccount.deposit(economyService.getDefaultCurrency(), BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause()); + claim.getData().getEconomyData().addBankTransaction( + new GDBankTransaction(BankTransactionType.WITHDRAW_SUCCESS, playerData.playerID, Instant.now(), amount)); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_WITHDRAW_NO_FUNDS, + ImmutableMap.of( + "balance", bankAccount.getBalance(economyService.getDefaultCurrency()), + "amount", amount)); + GriefDefenderPlugin.sendMessage(src, message); + claim.getData().getEconomyData() + .addBankTransaction(new GDBankTransaction(BankTransactionType.WITHDRAW_FAIL, playerData.playerID, Instant.now(), amount)); + return; + } + } else if (command.equalsIgnoreCase("deposit")) { + TransactionResult + result = + playerAccount.withdraw(economyService.getDefaultCurrency(), BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() == ResultType.SUCCESS) { + double depositAmount = amount; + if (claim.getData().isExpired()) { + final double taxBalance = claim.getEconomyData().getTaxBalance(); + depositAmount -= claim.getEconomyData().getTaxBalance(); + if (depositAmount >= 0) { + claim.getEconomyData().addBankTransaction(new GDBankTransaction(BankTransactionType.TAX_SUCCESS, Instant.now(), taxBalance)); + claim.getEconomyData().setTaxPastDueDate(null); + claim.getEconomyData().setTaxBalance(0); + claim.getInternalClaimData().setExpired(false); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TAX_CLAIM_PAID_BALANCE, + ImmutableMap.of( + "amount", taxBalance)); + GriefDefenderPlugin.sendMessage(src, message); + if (depositAmount == 0) { + return; + } + } else { + final double newTaxBalance = Math.abs(depositAmount); + claim.getEconomyData().setTaxBalance(newTaxBalance); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TAX_CLAIM_PAID_PARTIAL, + ImmutableMap.of( + "amount", depositAmount, + "balance", newTaxBalance)); + GriefDefenderPlugin.sendMessage(src, message); + return; + } + } + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_DEPOSIT, ImmutableMap.of( + "amount", depositAmount)); + GriefDefenderPlugin.sendMessage(src, message); + bankAccount.deposit(economyService.getDefaultCurrency(), BigDecimal.valueOf(depositAmount), Sponge.getCauseStackManager().getCurrentCause()); + claim.getData().getEconomyData().addBankTransaction( + new GDBankTransaction(BankTransactionType.DEPOSIT_SUCCESS, playerData.playerID, Instant.now(), depositAmount)); + } else { + GriefDefenderPlugin.sendMessage(src, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_WITHDRAW_NO_FUNDS)); + claim.getData().getEconomyData() + .addBankTransaction(new GDBankTransaction(BankTransactionType.DEPOSIT_FAIL, playerData.playerID, Instant.now(), amount)); + return; + } + } + } + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_NO_PERMISSION, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(src, message); + } + } + + public static void displayClaimBankInfo(CommandSource src, GDClaim claim) { + displayClaimBankInfo(src, claim, false, false); + } + + public static void displayClaimBankInfo(CommandSource src, GDClaim claim, boolean checkTown, boolean returnToClaimInfo) { + final EconomyService economyService = GriefDefenderPlugin.getInstance().economyService.orElse(null); + if (economyService == null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().ECONOMY_NOT_INSTALLED); + return; + } + + if (checkTown && !claim.isInTown()) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().TOWN_NOT_IN); + return; + } + + if (!checkTown && (claim.isSubdivision() || claim.isAdminClaim())) { + return; + } + + final GDClaim town = claim.getTownClaim(); + Account bankAccount = checkTown ? town.getEconomyAccount() : claim.getEconomyAccount(); + if (bankAccount == null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().ECONOMY_VIRTUAL_NOT_SUPPORTED); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(claim.getWorld(), claim.getOwnerUniqueId()); + final double claimBalance = bankAccount.getBalance(economyService.getDefaultCurrency()).doubleValue(); + double taxOwed = -1; + final double playerTaxRate = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), (Player) src, Options.TAX_RATE, claim); + if (checkTown) { + if (!town.getOwnerUniqueId().equals(playerData.playerID)) { + for (Claim playerClaim : playerData.getInternalClaims()) { + GDClaim playerTown = (GDClaim) playerClaim.getTown().orElse(null); + if (!playerClaim.isTown() && playerTown != null && playerTown.getUniqueId().equals(claim.getUniqueId())) { + taxOwed += playerTown.getClaimBlocks() * playerTaxRate; + } + } + } else { + taxOwed = town.getClaimBlocks() * playerTaxRate; + } + } else { + taxOwed = claim.getClaimBlocks() * playerTaxRate; + } + + final GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(claim.getWorld().getProperties()); + final ZonedDateTime withdrawDate = TaskUtil.getNextTargetZoneDate(activeConfig.getConfig().claim.taxApplyHour, 0, 0); + Duration duration = Duration.between(Instant.now().truncatedTo(ChronoUnit.SECONDS), withdrawDate.toInstant()) ; + final long s = duration.getSeconds(); + final String timeLeft = String.format("%d:%02d:%02d", s / 3600, (s % 3600) / 60, (s % 60)); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BANK_INFO, + ImmutableMap.of( + "balance", claimBalance, + "tax-amount", taxOwed, + "time-remaining", timeLeft, + "tax-balance", claim.getData().getEconomyData().getTaxBalance())); + Component transactions = TextComponent.builder("") + .append(MessageCache.getInstance().BANK_TITLE_TRANSACTIONS.color(TextColor.AQUA).decoration(TextDecoration.ITALIC, true)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createBankTransactionsConsumer(src, claim, checkTown, returnToClaimInfo)))) + .hoverEvent(HoverEvent.showText(MessageCache.getInstance().BANK_CLICK_VIEW_TRANSACTIONS)) + .build(); + List<Component> textList = new ArrayList<>(); + if (returnToClaimInfo) { + textList.add(TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMINFO_UI_RETURN_CLAIMINFO.color(TextColor.AQUA)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createCommandConsumer(src, "claiminfo", "")))).build()); + } + textList.add(message); + textList.add(transactions); + PaginationList.Builder builder = PaginationList.builder() + .title(TextComponent.of("Bank Info", TextColor.AQUA)).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList); + builder.sendTo(src); + } + + public static Consumer<CommandSource> createBankTransactionsConsumer(CommandSource src, GDClaim claim, boolean checkTown, boolean returnToClaimInfo) { + return settings -> { + List<String> bankTransactions = new ArrayList<>(claim.getData().getEconomyData().getBankTransactionLog()); + Collections.reverse(bankTransactions); + List<Component> textList = new ArrayList<>(); + textList.add(TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMINFO_UI_RETURN_BANKINFO.color(TextColor.AQUA)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(consumer -> { displayClaimBankInfo(src, claim, checkTown, returnToClaimInfo); }))).build()); + Gson gson = new Gson(); + for (String transaction : bankTransactions) { + GDBankTransaction bankTransaction = gson.fromJson(transaction, GDBankTransaction.class); + final Duration duration = Duration.between(bankTransaction.timestamp, Instant.now().truncatedTo(ChronoUnit.SECONDS)) ; + final long s = duration.getSeconds(); + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(bankTransaction.source); + final String timeLeft = String.format("%dh %02dm %02ds", s / 3600, (s % 3600) / 60, (s % 60)) + " ago"; + textList.add(TextComponent.builder("") + .append(bankTransaction.type.name(), getTransactionColor(bankTransaction.type)) + .append(" | ", TextColor.BLUE) + .append(TextComponent.of(String.valueOf(bankTransaction.amount))) + .append(" | ", TextColor.BLUE) + .append(timeLeft, TextColor.GRAY) + .append(user == null ? TextComponent.empty() : TextComponent.builder("") + .append(" | ", TextColor.BLUE) + .append(user.getName(), TextColor.LIGHT_PURPLE) + .build()) + .build()); + } + textList.add(TextComponent.builder("") + .append("\n[") + .append(MessageCache.getInstance().CLAIMINFO_UI_RETURN_BANKINFO.color(TextColor.AQUA)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(CommandHelper.createCommandConsumer(src, "claimbank", "")))).build()); + PaginationList.Builder builder = PaginationList.builder() + .title(MessageCache.getInstance().BANK_TITLE_TRANSACTIONS.color(TextColor.AQUA)).padding(TextComponent.builder(" ").decoration(TextDecoration.STRIKETHROUGH, true).build()).contents(textList); + builder.sendTo(src); + }; + } + + public static TextColor getTransactionColor(BankTransactionType type) { + switch (type) { + case DEPOSIT_SUCCESS : + case TAX_SUCCESS : + case WITHDRAW_SUCCESS : + return TextColor.GREEN; + case DEPOSIT_FAIL : + case TAX_FAIL : + case WITHDRAW_FAIL : + return TextColor.RED; + default : + return TextColor.GREEN; + } + } + + public static Component getBaseOptionOverlayText(String option) { + String baseFlag = option.replace(GDPermissions.OPTION_BASE + ".", ""); + int endIndex = baseFlag.indexOf("."); + if (endIndex != -1) { + baseFlag = baseFlag.substring(0, endIndex); + } + + final Option flag = GriefDefender.getRegistry().getType(Option.class, baseFlag).orElse(null); + if (flag == null) { + return MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_FOUND, ImmutableMap.of( + "option", baseFlag)); + } + + return flag.getDescription(); + } + + public static TrustType getTrustType(String type) { + switch (type.toLowerCase()) { + case "accessor" : + return TrustTypes.ACCESSOR; + case "builder" : + return TrustTypes.BUILDER; + case "container" : + return TrustTypes.CONTAINER; + case "manager" : + return TrustTypes.MANAGER; + case "none" : + return TrustTypes.NONE; + default : + return null; + } + } + + public static boolean checkTrustPermission(Player player, TrustType type) { + if (type == TrustTypes.ACCESSOR) { + return player.hasPermission(GDPermissions.GIVE_ACCESS_TRUST); + } + if (type == TrustTypes.BUILDER) { + return player.hasPermission(GDPermissions.GIVE_BUILDER_TRUST); + } + if (type == TrustTypes.CONTAINER) { + return player.hasPermission(GDPermissions.GIVE_CONTAINER_TRUST); + } + if (type == TrustTypes.MANAGER) { + return player.hasPermission(GDPermissions.GIVE_MANAGER_TRUST); + } + if (type == TrustTypes.NONE) { + return player.hasPermission(GDPermissions.REMOVE_TRUST); + } + + return true; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandPagination.java b/sponge/src/main/java/com/griefdefender/command/CommandPagination.java new file mode 100644 index 0000000..fb5ed29 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandPagination.java @@ -0,0 +1,36 @@ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.Description; +import com.griefdefender.internal.pagination.ActivePagination; +import com.griefdefender.internal.pagination.GDPaginationHolder; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; + +public class CommandPagination extends BaseCommand { + + @CommandAlias("gd:pagination") + @Description("Used internally by GD for pagination purposes.") + public void execute(CommandSource src, String[] args) throws CommandException { + String id = args[0]; + final ActivePagination activePagination = GDPaginationHolder.getInstance().getActivePagination(src, id); + if (activePagination == null) { + TextAdapter.sendComponent(src, TextComponent.of("Source " + src.getName() + " has no paginations!", TextColor.RED)); + return; + } + String action = args[1]; + if (action.equals("page")) { + activePagination.currentPage(); + } else if (action.equals("next")) { + activePagination.nextPage(); + } else if (action.equals("prev")) { + activePagination.previousPage(); + } else { + int page = Integer.parseInt(action); + activePagination.specificPage(page); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandPlayerInfo.java b/sponge/src/main/java/com/griefdefender/command/CommandPlayerInfo.java new file mode 100644 index 0000000..543840c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandPlayerInfo.java @@ -0,0 +1,273 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.PlayerUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.data.manipulator.mutable.entity.JoinData; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.world.storage.WorldProperties; + +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_PLAYER_INFO_BASE) +public class CommandPlayerInfo extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("playerinfo") + @Description("Gets information about a player.") + @Syntax("[<player>|<player> <world>]") + @Subcommand("player info") + public void execute(CommandSource src, @Optional String[] args) throws InvalidCommandArgument { + GDPermissionUser user = null; + WorldProperties worldProperties = null; + if (args.length > 0) { + user = PermissionHolderCache.getInstance().getOrCreateUser(args[0]); + if (user == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_PLAYER_NOT_FOUND, + ImmutableMap.of("player", args[0]))); + return; + } + if (args.length > 1) { + worldProperties = Sponge.getServer().getWorldProperties(args[1]).orElse(null); + if (worldProperties == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_WORLD_NOT_FOUND, + ImmutableMap.of("world", args[1]))); + return; + } + } + } + + if (user == null) { + if (!(src instanceof Player)) { + GriefDefenderPlugin.sendMessage(src, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_INVALID_PLAYER)); + return; + } + + user = PermissionHolderCache.getInstance().getOrCreateUser((User) src); + if (worldProperties == null) { + worldProperties = ((Player) src).getWorld().getProperties(); + } + } + if (worldProperties == null) { + worldProperties = Sponge.getServer().getDefaultWorld().get(); + } + + // otherwise if no permission to delve into another player's claims data or self + if ((user != null && user != src && !src.hasPermission(GDPermissions.COMMAND_PLAYER_INFO_OTHERS))) { + TextAdapter.sendComponent(src, MessageCache.getInstance().PERMISSION_PLAYER_VIEW_OTHERS); + } + + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(worldProperties.getUniqueId(), user.getUniqueId()); + boolean useGlobalData = BaseStorage.USE_GLOBAL_PLAYER_STORAGE; + List<Claim> claimList = new ArrayList<>(); + for (Claim claim : playerData.getInternalClaims()) { + if (useGlobalData) { + claimList.add(claim); + } else { + if (claim.getWorldUniqueId().equals(worldProperties.getUniqueId())) { + claimList.add(claim); + } + } + } + Component claimSizeLimit = TextComponent.of("none", TextColor.GRAY); + if (playerData.getMaxClaimX(ClaimTypes.BASIC) != 0 || playerData.getMaxClaimY(ClaimTypes.BASIC) != 0 || playerData.getMaxClaimZ(ClaimTypes.BASIC) != 0) { + claimSizeLimit = TextComponent.of(playerData.getMaxClaimX(ClaimTypes.BASIC) + "," + playerData.getMaxClaimY(ClaimTypes.BASIC) + "," + playerData.getMaxClaimZ(ClaimTypes.BASIC), TextColor.GRAY); + } + + final double claimableChunks = GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME ? (playerData.getRemainingClaimBlocks() / 65536.0) : (playerData.getRemainingClaimBlocks() / 256.0); + final Component uuidText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_UUID, + ImmutableMap.of("id", user.getUniqueId().toString())); + final Component worldText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_WORLD, + ImmutableMap.of("name", worldProperties.getWorldName())); + final Component sizeLimitText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_CLAIM_SIZE_LIMIT, + ImmutableMap.of("limit", claimSizeLimit)); + final Component initialBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_INITIAL, + ImmutableMap.of("amount", String.valueOf(playerData.getInitialClaimBlocks()))); + final Component accruedBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_ACCRUED, + ImmutableMap.of( + "amount", String.valueOf(playerData.getAccruedClaimBlocks()), + "block_amount", String.valueOf(playerData.getBlocksAccruedPerHour()))); + final Component maxAccruedBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_MAX_ACCRUED, + ImmutableMap.of("amount", String.valueOf(playerData.getMaxAccruedClaimBlocks()))); + final Component bonusBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_BONUS, + ImmutableMap.of("amount", String.valueOf(playerData.getBonusClaimBlocks()))); + final Component remainingBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_REMAINING, + ImmutableMap.of("amount", String.valueOf(playerData.getRemainingClaimBlocks()))); + final Component economyBlockAvailablePurchaseText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_ECONOMY_BLOCK_AVAILABLE_PURCHASE, + ImmutableMap.of("amount", String.valueOf(playerData.getRemainingClaimBlocks()))); + final Component economyBlockCostText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_ECONOMY_BLOCK_COST, + ImmutableMap.of("amount", String.valueOf("$" + playerData.getInternalEconomyBlockCost()))); + final Component economyBlockSellReturnText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_ECONOMY_BLOCK_SELL_RETURN, + ImmutableMap.of("amount", String.valueOf("$" + playerData.getEconomyClaimBlockReturn()))); + final Component minMaxLevelText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_CLAIM_LEVEL, + ImmutableMap.of("level", String.valueOf(playerData.getMinClaimLevel() + "-" + playerData.getMaxClaimLevel()))); + final Component abandonRatioText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_ABANDON_RETURN_RATIO, + ImmutableMap.of("ratio", String.valueOf(playerData.getAbandonedReturnRatio(ClaimTypes.BASIC)))); + final Component totalTaxText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_TAX_TOTAL, + ImmutableMap.of("amount", String.valueOf(playerData.getInitialClaimBlocks()))); + final Component totalBlockText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_BLOCK_TOTAL, + ImmutableMap.of("amount", String.valueOf(playerData.getInitialClaimBlocks()))); + final Component totalClaimableChunkText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_CHUNK_TOTAL, + ImmutableMap.of("amount", String.valueOf(Math.round(claimableChunks * 100.0)/100.0))); + final Component totalClaimText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_CLAIM_TOTAL, + ImmutableMap.of("amount", String.valueOf(claimList.size()))); + + List<Component> claimsTextList = Lists.newArrayList(); + claimsTextList.add(uuidText); + claimsTextList.add(worldText); + claimsTextList.add(sizeLimitText); + claimsTextList.add(initialBlockText); + claimsTextList.add(accruedBlockText); + claimsTextList.add(maxAccruedBlockText); + claimsTextList.add(bonusBlockText); + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + claimsTextList.add(economyBlockAvailablePurchaseText); + claimsTextList.add(economyBlockCostText); + claimsTextList.add(economyBlockSellReturnText); + } else { + claimsTextList.add(remainingBlockText); + } + claimsTextList.add(minMaxLevelText); + claimsTextList.add(abandonRatioText); + final int townLimit = playerData.getCreateClaimLimit(ClaimTypes.TOWN); + final int basicLimit = playerData.getCreateClaimLimit(ClaimTypes.BASIC); + final int subLimit = playerData.getCreateClaimLimit(ClaimTypes.SUBDIVISION); + String townLimitText = townLimit < 0 ? "∞" : String.valueOf(townLimit); + String basicLimitText = basicLimit < 0 ? "∞" : String.valueOf(basicLimit); + String subLimitText = subLimit < 0 ? "∞" : String.valueOf(subLimit); + + Component claimCreateLimits = TextComponent.builder("") + .append("TOWN", TextColor.GRAY) + .append(" : ") + .append(townLimitText, TextColor.GREEN) + .append(" BASIC", TextColor.GRAY) + .append(" : ") + .append(basicLimitText, TextColor.GREEN) + .append(" SUB", TextColor.GRAY) + .append(" : ") + .append(subLimitText, TextColor.GREEN) + .build(); + claimsTextList.add(claimCreateLimits); + if (GriefDefenderPlugin.getGlobalConfig().getConfig().claim.bankTaxSystem) { + Component townTaxRate = TextComponent.builder("") + .append("TOWN", TextColor.GRAY) + .append(" : ") + .append(String.valueOf(playerData.getTaxRate(ClaimTypes.TOWN)), TextColor.GREEN) + .build(); + // TODO + //TextColors.GRAY, " BASIC", TextColors.WHITE, " : ", TextColors.GREEN, playerData.optionTaxRateTownBasic, + //TextColors.GRAY, " SUB", TextColors.WHITE, " : ", TextColors.GREEN, playerData.getTaxRate(type)); + Component claimTaxRate = TextComponent.builder("") + .append("BASIC", TextColor.GRAY) + .append(" : ") + .append(String.valueOf(playerData.getTaxRate(ClaimTypes.BASIC)), TextColor.GREEN) + .append(" SUB", TextColor.GRAY) + .append(" : ") + .append(String.valueOf(playerData.getTaxRate(ClaimTypes.SUBDIVISION)), TextColor.GREEN) + .build(); + String taxRate = "N/A"; + if (src instanceof Player) { + Player player = (Player) src; + if (player.getUniqueId().equals(user.getUniqueId())) { + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + if (claim != null && !claim.isWilderness()) { + final double playerTaxRate = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), user, Options.TAX_RATE, claim); + taxRate = String.valueOf(playerTaxRate); + } + } + } + final Component currentTaxRateText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_TAX_CURRENT_RATE, + ImmutableMap.of("rate", taxRate)); + final Component globalTownTaxText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_TAX_GLOBAL_TOWN_RATE, + ImmutableMap.of("rate", townTaxRate)); + final Component globalClaimTaxText = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_TAX_GLOBAL_CLAIM_RATE, + ImmutableMap.of("rate", claimTaxRate)); + claimsTextList.add(currentTaxRateText); + claimsTextList.add(globalTownTaxText); + claimsTextList.add(globalClaimTaxText); + claimsTextList.add(totalTaxText); + } + claimsTextList.add(totalBlockText); + claimsTextList.add(totalClaimableChunkText); + claimsTextList.add(totalClaimText); + JoinData joinData = user.getOfflinePlayer().getOrCreate(JoinData.class).orElse(null); + if (joinData != null && joinData.lastPlayed().exists()) { + Date lastActive = null; + try { + lastActive = Date.from(joinData.lastPlayed().get()); + } catch(DateTimeParseException ex) { + // ignore + } + if (lastActive != null) { + claimsTextList.add(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYERINFO_UI_LAST_ACTIVE, + ImmutableMap.of("date", lastActive))); + } + } + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(MessageCache.getInstance().PLAYERINFO_UI_TITLE).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(claimsTextList); + paginationBuilder.sendTo(src); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandRestoreClaim.java b/sponge/src/main/java/com/griefdefender/command/CommandRestoreClaim.java new file mode 100644 index 0000000..3dd00f0 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandRestoreClaim.java @@ -0,0 +1,96 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_RESTORE_CLAIM) +public class CommandRestoreClaim extends BaseCommand { + + @CommandAlias("restoreclaim|claimrestore") + @Description("Restores claim to its natural state. Use with caution.") + @Subcommand("claim restore") + public void execute(Player player) { + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_FOUND); + return; + } + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_CLAIM_DELETE, + ImmutableMap.of( + "type", claim.getType().getName())); + + if (!player.hasPermission(GDPermissions.DELETE_CLAIM_ADMIN)) { + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + displayConfirmationConsumer(player, claim); + } + + private static void displayConfirmationConsumer(CommandSource src, GDClaim claim) { + final Component schematicConfirmationText = TextComponent.builder("") + .append("Are you sure you want to restore this claim?") + .append("\n[") + .append("Confirm", TextColor.GREEN) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createConfirmationConsumer(src, claim)))) + .hoverEvent(HoverEvent.showText(TextComponent.of("Clicking confirm will restore ENTIRE claim back to default world gen state. Use cautiously!"))).build(); + TextAdapter.sendComponent(src, schematicConfirmationText); + } + + private static Consumer<CommandSource> createConfirmationConsumer(CommandSource src, GDClaim claim) { + return confirm -> { + BlockUtil.getInstance().restoreClaim(claim); + final Component message = MessageCache.getInstance().CLAIM_RESTORE_SUCCESS; + GriefDefenderPlugin.sendMessage(src, message); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandRestoreNature.java b/sponge/src/main/java/com/griefdefender/command/CommandRestoreNature.java new file mode 100644 index 0000000..1e3613f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandRestoreNature.java @@ -0,0 +1,56 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_RESTORE_NATURE) +public class CommandRestoreNature extends BaseCommand { + + @CommandAlias("modenature") + @Description("Switches the shovel tool to restoration mode.") + @Subcommand("mode nature") + public void execute(Player player) { + if (true) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().FEATURE_NOT_AVAILABLE); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + playerData.shovelMode = ShovelTypes.RESTORE; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_NATURE); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandSetAccruedClaimBlocks.java b/sponge/src/main/java/com/griefdefender/command/CommandSetAccruedClaimBlocks.java new file mode 100644 index 0000000..1a73cec --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandSetAccruedClaimBlocks.java @@ -0,0 +1,115 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.InvalidCommandArgument; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.storage.WorldProperties; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_SET_ACCRUED_CLAIM_BLOCKS) +public class CommandSetAccruedClaimBlocks extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("scb|setaccruedblocks") + @Description("Updates a player's accrued claim block total.") + @Syntax("<player> <amount> [world]") + @Subcommand("player setaccruedblocks") + public void execute(CommandSource src, String player, int amount, @Optional String worldName) throws InvalidCommandArgument { + GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + if (user == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_PLAYER_NOT_FOUND, + ImmutableMap.of("player", player))); + return; + } + World world = null; + if (worldName != null) { + world = Sponge.getServer().getWorld(worldName).orElse(null); + if (world == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_WORLD_NOT_FOUND, + ImmutableMap.of("world", worldName))); + return; + } + } else { + if (src instanceof Player) { + world = ((Player) src).getWorld(); + } else { + // use default + world = Sponge.getServer().getWorlds().iterator().next(); + } + if (world == null) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_WORLD_NOT_FOUND, + ImmutableMap.of("world", worldName))); + return; + } + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // set player's blocks + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(world.getUniqueId(), user.getUniqueId()); + if (!playerData.setAccruedClaimBlocks(amount)) { + TextAdapter.sendComponent(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PLAYER_ACCRUED_BLOCKS_EXCEEDED, + ImmutableMap.of( + "player", user.getName(), + "total", playerData.getAccruedClaimBlocks(), + "amount", amount))); + return; + } + + playerData.getStorageData().save(); + GriefDefenderPlugin.sendMessage(src, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ADJUST_ACCRUED_BLOCKS_SUCCESS, + ImmutableMap.of( + "player", user.getName(), + "total", playerData.getAccruedClaimBlocks(), + "amount", amount))); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTownChat.java b/sponge/src/main/java/com/griefdefender/command/CommandTownChat.java new file mode 100644 index 0000000..89ec4e4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTownChat.java @@ -0,0 +1,63 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissions; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TOWN_CHAT) +public class CommandTownChat extends BaseCommand { + + @CommandAlias("townchat") + @Description("Toggles town chat.") + @Subcommand("town chat") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(player.getLocation()); + if (!claim.isInTown()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_NOT_IN); + return; + } + + playerData.townChat = !playerData.townChat; + + // toggle ignore claims mode on or off + if (!playerData.townChat) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_CHAT_DISABLED); + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_CHAT_ENABLED); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTownTag.java b/sponge/src/main/java/com/griefdefender/command/CommandTownTag.java new file mode 100644 index 0000000..2d256b3 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTownTag.java @@ -0,0 +1,88 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TOWN_TAG) +public class CommandTownTag extends BaseCommand { + + @CommandAlias("towntag") + @Description("Sets the tag of your town.") + @Syntax("<tag>") + @Subcommand("town tag") + public void execute(Player player, String tag) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (claim == null || !claim.isInTown()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TOWN_NOT_IN); + return; + } + + final GDClaim town = claim.getTownClaim(); + final Component result = town.allowEdit(player); + if (result != null) { + GriefDefenderPlugin.sendMessage(player, result); + return; + } + + TextComponent name = LegacyComponentSerializer.legacy().deserialize(tag, '&'); + if (name == TextComponent.empty() || name.content().equals("clear")) { + town.getTownData().setTownTag(null); + } else { + town.getTownData().setTownTag(name); + } + + town.getInternalClaimData().setRequiresSave(true); + Component resultMessage = null; + if (!claim.getTownData().getTownTag().isPresent()) { + resultMessage = MessageCache.getInstance().TOWN_TAG_CLEAR; + } else { + resultMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TOWN_TAG, + ImmutableMap.of( + "tag", name)); + } + TextAdapter.sendComponent(player, resultMessage); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTrustGroup.java b/sponge/src/main/java/com/griefdefender/command/CommandTrustGroup.java new file mode 100644 index 0000000..b0b5507 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTrustGroup.java @@ -0,0 +1,145 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.Context; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDGroupTrustClaimEvent; +import com.griefdefender.permission.GDPermissionGroup; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TRUST_GROUP) +public class CommandTrustGroup extends BaseCommand { + + @CommandCompletion("@gdgroups @gdtrusttypes @gddummy") + @CommandAlias("trustgroup") + @Description("Grants a group access to your claim." + + "\nAccessor: access to interact with all blocks except inventory." + + "\nContainer: access to interact with all blocks including inventory." + + "\nBuilder: access to everything above including ability to place and break blocks." + + "\nManager: access to everything above including ability to manage claim settings.") + @Syntax("<group> <accessor|builder|container|manager>") + @Subcommand("trust group") + public void execute(Player player, String groupName, String type) { + final TrustType trustType = CommandHelper.getTrustType(type); + if (trustType == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_INVALID); + return; + } + + final GDPermissionGroup group = PermissionHolderCache.getInstance().getOrCreateGroup(groupName); + if (group == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_GROUP, ImmutableMap.of( + "group", groupName))); + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (!playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + //check permission here + if(claim.allowGrantPermission(player) != null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, + ImmutableMap.of( + "owner", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDGroupTrustClaimEvent.Remove event = + new GDGroupTrustClaimEvent.Remove(claim, ImmutableList.of(group.getName()), TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", group)))); + return; + } + + final String permission = CommandHelper.getTrustPermission(trustType); + Set<Context> contexts = new HashSet<>(); + contexts.add(claim.getContext()); + final List<String> groupTrustList = claim.getGroupTrustList(trustType); + if (!groupTrustList.contains(group.getName())) { + groupTrustList.add(group.getName()); + } else { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_ALREADY_HAS, + ImmutableMap.of( + "target", group.getName(), + "type", trustType.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + PermissionUtil.getInstance().setPermissionValue(group, permission, Tristate.TRUE, contexts); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_GRANT, ImmutableMap.of( + "target", group.getName(), + "type", trustType.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTrustGroupAll.java b/sponge/src/main/java/com/griefdefender/command/CommandTrustGroupAll.java new file mode 100644 index 0000000..47363a0 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTrustGroupAll.java @@ -0,0 +1,132 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDGroupTrustClaimEvent; +import com.griefdefender.permission.GDPermissionGroup; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TRUSTALL_GROUP) +public class CommandTrustGroupAll extends BaseCommand { + + @CommandCompletion("@gdgroups @gdtrusttypes @gddummy") + @CommandAlias("trustallgroup") + @Description("Grants a group access to all your claims." + + "\nAccessor: access to interact with all blocks except inventory." + + "\nContainer: access to interact with all blocks including inventory." + + "\nBuilder: access to everything above including ability to place and break blocks." + + "\nManager: access to everything above including ability to manage claim settings.") + @Syntax("<group> <accessor|builder|container|manager>") + @Subcommand("trustall group") + public void execute(Player player, String target, String type) { + final TrustType trustType = CommandHelper.getTrustType(type); + if (trustType == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_INVALID); + return; + } + + final GDPermissionGroup group = PermissionHolderCache.getInstance().getOrCreateGroup(target); + + // validate player argument + if (group == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_GROUP, ImmutableMap.of( + "group", target))); + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + Set<Claim> claimList = null; + if (playerData != null) { + claimList = playerData.getInternalClaims(); + } + + if (playerData == null || claimList == null || claimList.size() == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_NO_CLAIMS); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDGroupTrustClaimEvent.Add + event = new GDGroupTrustClaimEvent.Add(new ArrayList<>(claimList), ImmutableList.of(group.getName()), trustType); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", group)))); + return; + } + + for (Claim claim : claimList) { + this.addAllGroupTrust(claim, group, trustType); + } + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_INDIVIDUAL_ALL_CLAIMS, + ImmutableMap.of( + "player", group.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } + + private void addAllGroupTrust(Claim claim, GDPermissionGroup holder, TrustType trustType) { + GDClaim gdClaim = (GDClaim) claim; + List<String> trustList = gdClaim.getGroupTrustList(trustType); + if (!trustList.contains(holder.getFriendlyName())) { + trustList.add(holder.getFriendlyName()); + } + + gdClaim.getInternalClaimData().setRequiresSave(true); + gdClaim.getInternalClaimData().save(); + for (Claim child : gdClaim.children) { + this.addAllGroupTrust(child, holder, trustType); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTrustList.java b/sponge/src/main/java/com/griefdefender/command/CommandTrustList.java new file mode 100644 index 0000000..77d8ec5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTrustList.java @@ -0,0 +1,253 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.pagination.PaginationList; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_LIST_TRUST) +public class CommandTrustList extends BaseCommand { + + @CommandAlias("trustlist") + @Description("Lists permissions for the claim you're standing in.") + @Subcommand("trust list") + public void execute(Player player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + showTrustList(player, claim, TrustTypes.NONE); + } + + public static void showTrustList(CommandSource src, GDClaim claim, TrustType type) { + final Component whiteOpenBracket = TextComponent.of("[", TextColor.AQUA); + final Component whiteCloseBracket = TextComponent.of("]", TextColor.AQUA); + final Component showAllText = MessageCache.getInstance().TRUST_CLICK_SHOW_LIST; + final Component showAccessorText = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", MessageCache.getInstance().TITLE_ACCESSOR.color(TextColor.YELLOW))); + final Component showContainerText = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", MessageCache.getInstance().TITLE_CONTAINER.color(TextColor.LIGHT_PURPLE))); + final Component showBuilderText = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", MessageCache.getInstance().TITLE_BUILDER.color(TextColor.GREEN))); + final Component showManagerText = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.UI_CLICK_FILTER_TYPE, + ImmutableMap.of("type", MessageCache.getInstance().TITLE_MANAGER.color(TextColor.GOLD))); + final Component allTypeText = TextComponent.builder("") + .append(type == TrustTypes.NONE ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("ALL") + .append(whiteCloseBracket) + .build() : TextComponent.builder("") + .append("ALL",TextColor.GRAY) + .build()) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createTrustConsumer(src, claim, TrustTypes.NONE)))) + .hoverEvent(HoverEvent.showText(showAllText)).build(); + final Component accessorTrustText = TextComponent.builder("") + .append(type == TrustTypes.ACCESSOR ? TextComponent.builder("") + .append(whiteOpenBracket) + .append(MessageCache.getInstance().TITLE_ACCESSOR.color(TextColor.YELLOW)) + .append(whiteCloseBracket) + .build() : MessageCache.getInstance().TITLE_ACCESSOR.color(TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createTrustConsumer(src, claim, TrustTypes.ACCESSOR)))) + .hoverEvent(HoverEvent.showText(showAccessorText)).build(); + final Component builderTrustText = TextComponent.builder("") + .append(type == TrustTypes.BUILDER ? TextComponent.builder("") + .append(whiteOpenBracket) + .append(MessageCache.getInstance().TITLE_BUILDER.color(TextColor.GREEN)) + .append(whiteCloseBracket) + .build() : MessageCache.getInstance().TITLE_BUILDER.color(TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createTrustConsumer(src, claim, TrustTypes.BUILDER)))) + .hoverEvent(HoverEvent.showText(showBuilderText)).build(); + final Component containerTrustText = TextComponent.builder("") + .append(type == TrustTypes.CONTAINER ? TextComponent.builder("") + .append(whiteOpenBracket) + .append(MessageCache.getInstance().TITLE_CONTAINER.color(TextColor.LIGHT_PURPLE)) + .append(whiteCloseBracket) + .build() : MessageCache.getInstance().TITLE_CONTAINER.color(TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createTrustConsumer(src, claim, TrustTypes.CONTAINER)))) + .hoverEvent(HoverEvent.showText(showContainerText)).build(); + final Component managerTrustText = TextComponent.builder("") + .append(type == TrustTypes.MANAGER ? TextComponent.builder("") + .append(whiteOpenBracket) + .append("MANAGER", TextColor.GOLD) + .append(whiteCloseBracket) + .build() : MessageCache.getInstance().TITLE_MANAGER.color(TextColor.GRAY)) + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(createTrustConsumer(src, claim, TrustTypes.MANAGER)))) + .hoverEvent(HoverEvent.showText(showManagerText)).build(); + final Component claimTrustHead = TextComponent.builder() + .append(" ") + .append(MessageCache.getInstance().LABEL_DISPLAYING.color(TextColor.AQUA)) + .append(" ") + .append(allTypeText) + .append(" ") + .append(accessorTrustText) + .append(" ") + .append(builderTrustText) + .append(" ") + .append(containerTrustText) + .append(" ") + .append(managerTrustText) + .build(); + + List<UUID> userIdList = new ArrayList<>(claim.getUserTrusts()); + List<Component> trustList = new ArrayList<>(); + trustList.add(TextComponent.empty()); + + if (type == TrustTypes.NONE) { + // check highest trust first + for (UUID uuid : claim.getInternalClaimData().getManagers()) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + trustList.add(TextComponent.of(user.getName(), TextColor.GOLD)); + userIdList.remove(user.getUniqueId()); + } + + for (UUID uuid : claim.getInternalClaimData().getBuilders()) { + if (!userIdList.contains(uuid)) { + continue; + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + trustList.add(TextComponent.of(user.getName(), TextColor.GREEN)); + userIdList.remove(uuid); + } + + /*for (String group : claim.getInternalClaimData().getManagerGroups()) { + permissions.append(SPACE_TEXT, Text.of(group)); + }*/ + + for (UUID uuid : claim.getInternalClaimData().getContainers()) { + if (!userIdList.contains(uuid)) { + continue; + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + trustList.add(TextComponent.of(user.getName(), TextColor.LIGHT_PURPLE)); + userIdList.remove(uuid); + } + + /* for (String group : claim.getInternalClaimData().getBuilderGroups()) { + permissions.append(SPACE_TEXT, Text.of(group)); + }*/ + + for (UUID uuid : claim.getInternalClaimData().getAccessors()) { + if (!userIdList.contains(uuid)) { + continue; + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + trustList.add(TextComponent.of(user.getName(), TextColor.YELLOW)); + userIdList.remove(uuid); + } + + /*for (String group : claim.getInternalClaimData().getContainerGroups()) { + permissions.append(SPACE_TEXT, Text.of(group)); + } + + player.sendMessage(permissions.build()); + permissions = Text.builder(">").color(TextColors.BLUE); + + for (UUID uuid : claim.getInternalClaimData().getAccessors()) { + User user = GriefDefenderPlugin.getOrCreateUser(uuid); + permissions.append(SPACE_TEXT, Text.of(user.getName())); + } + + for (String group : claim.getInternalClaimData().getAccessorGroups()) { + permissions.append(SPACE_TEXT, Text.of(group)); + }*/ + + } else { + for (UUID uuid : claim.getUserTrusts(type)) { + if (!userIdList.contains(uuid)) { + continue; + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + trustList.add(TextComponent.of(user.getName(), getTrustColor(type))); + userIdList.remove(uuid); + } + } + + int fillSize = 20 - (trustList.size() + 2); + for (int i = 0; i < fillSize; i++) { + trustList.add(TextComponent.of(" ")); + } + + PaginationList.Builder paginationBuilder = PaginationList.builder() + .title(claimTrustHead).padding(TextComponent.of(" ").decoration(TextDecoration.STRIKETHROUGH, true)).contents(trustList); + paginationBuilder.sendTo(src); + paginationBuilder.sendTo(src); + + } + + private static TextColor getTrustColor(TrustType type) { + if (type == TrustTypes.NONE) { + return TextColor.WHITE; + } + if (type == TrustTypes.ACCESSOR) { + return TextColor.YELLOW; + } + if (type == TrustTypes.BUILDER) { + return TextColor.GREEN; + } + if (type == TrustTypes.CONTAINER) { + return TextColor.LIGHT_PURPLE; + } + return TextColor.GOLD; + } + + private static Consumer<CommandSource> createTrustConsumer(CommandSource src, GDClaim claim, TrustType type) { + return consumer -> { + showTrustList(src, claim, type); + }; + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayer.java b/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayer.java new file mode 100644 index 0000000..2ba8976 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayer.java @@ -0,0 +1,167 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.List; +import java.util.UUID; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TRUST_PLAYER) +public class CommandTrustPlayer extends BaseCommand { + + @CommandCompletion("@gdplayers @gdtrusttypes @gddummy") + @CommandAlias("trust") + @Description("Grants a player access to your claim." + + "\nAccessor: access to interact with all blocks except inventory." + + "\nContainer: access to interact with all blocks including inventory." + + "\nBuilder: access to everything above including ability to place and break blocks." + + "\nManager: access to everything above including ability to manage claim settings.") + @Syntax("<player> [<accessor|builder|container|manager>]") + @Subcommand("trust player") + public void execute(Player player, String target, @Optional String type) { + TrustType trustType = null; + if (type == null) { + trustType = TrustTypes.BUILDER; + } else { + trustType = CommandHelper.getTrustType(type); + if (trustType == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_INVALID); + return; + } + } + + GDPermissionUser user = null; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + if (user == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", target))); + return; + } + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (!claim.getOwnerUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + if (user.getUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_SELF); + return; + } + + if (user != null && claim.getOwnerUniqueId().equals(user.getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_OWNER_ALREADY); + return; + } else { + //check permission here + if(claim.allowGrantPermission(player) != null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + if(trustType == TrustTypes.MANAGER) { + Component denyReason = claim.allowEdit(player); + if(denyReason != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_GRANT); + return; + } + } + } + + GDCauseStackManager.getInstance().pushCause(player); + GDUserTrustClaimEvent.Add + event = + new GDUserTrustClaimEvent.Add(claim, ImmutableList.of(user.getUniqueId()), trustType); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName()))))); + return; + } + + final List<UUID> trustList = claim.getUserTrustList(trustType); + if (trustList.contains(user.getUniqueId())) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_ALREADY_HAS, + ImmutableMap.of( + "target", user.getName(), + "type", trustType.getName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + trustList.add(user.getUniqueId()); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_GRANT, ImmutableMap.of( + "target", user.getName(), + "type", trustType.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayerAll.java b/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayerAll.java new file mode 100644 index 0000000..3abe47e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandTrustPlayerAll.java @@ -0,0 +1,151 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_TRUSTALL_PLAYER) +public class CommandTrustPlayerAll extends BaseCommand { + + @CommandCompletion("@gdplayers @gdtrusttypes @gddummy") + @CommandAlias("trustall") + @Description("Grants a player access to all your claims." + + "\nAccessor: access to interact with all blocks except inventory." + + "\nContainer: access to interact with all blocks including inventory." + + "\nBuilder: access to everything above including ability to place and break blocks." + + "\nManager: access to everything above including ability to manage claim settings.") + @Syntax("<player> <accessor|builder|container|manager>") + @Subcommand("trustall player") + public void execute(Player player, String target, @Optional String type) { + TrustType trustType = null; + if (type == null) { + trustType = TrustTypes.BUILDER; + } else { + trustType = CommandHelper.getTrustType(type); + if (trustType == null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_INVALID); + return; + } + } + + GDPermissionUser user; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + // validate player argument + if (user == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", target))); + return; + } + + if (user.getUniqueId().equals(player.getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_SELF); + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + Set<Claim> claimList = null; + if (playerData != null) { + claimList = playerData.getInternalClaims(); + } + + if (playerData == null || claimList == null || claimList.size() == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_NO_CLAIMS); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDUserTrustClaimEvent.Add + event = new GDUserTrustClaimEvent.Add(new ArrayList<>(claimList), ImmutableList.of(user.getUniqueId()), trustType); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName())))); + return; + } + + for (Claim claim : claimList) { + this.addAllUserTrust(claim, user, trustType); + } + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_INDIVIDUAL_ALL_CLAIMS, + ImmutableMap.of( + "player", user.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } + + private void addAllUserTrust(Claim claim, GDPermissionUser user, TrustType trustType) { + GDClaim gdClaim = (GDClaim) claim; + List<UUID> trustList = gdClaim.getUserTrustList(trustType); + if (!trustList.contains(user.getUniqueId())) { + trustList.add(user.getUniqueId()); + } + + gdClaim.getInternalClaimData().setRequiresSave(true); + gdClaim.getInternalClaimData().save(); + for (Claim child : gdClaim.children) { + this.addAllUserTrust(child, user, trustType); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroup.java b/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroup.java new file mode 100644 index 0000000..64a55cc --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroup.java @@ -0,0 +1,115 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDGroupTrustClaimEvent; +import com.griefdefender.permission.GDPermissionGroup; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.permission.Subject; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_UNTRUST_GROUP) +public class CommandUntrustGroup extends BaseCommand { + + @CommandCompletion("@gdgroups @gddummy") + @CommandAlias("untrustgroup") + @Description("Revokes group access to your claim.") + @Syntax("<group>") + @Subcommand("untrust group") + public void execute(Player player, String target) { + final GDPermissionGroup group = PermissionHolderCache.getInstance().getOrCreateGroup(target); + if (group == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_GROUP, ImmutableMap.of( + "group", target))); + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (!playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + //check permission here + if(claim.allowGrantPermission(player) != null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDGroupTrustClaimEvent.Remove event = + new GDGroupTrustClaimEvent.Remove(claim, ImmutableList.of(group.getName()), TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", group)))); + return; + } + + claim.removeAllTrustsFromGroup(group.getName()); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UNTRUST_INDIVIDUAL_SINGLE_CLAIM, + ImmutableMap.of( + "target", group)); + GriefDefenderPlugin.sendMessage(player, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroupAll.java b/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroupAll.java new file mode 100644 index 0000000..97d2776 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandUntrustGroupAll.java @@ -0,0 +1,118 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDGroupTrustClaimEvent; +import com.griefdefender.permission.GDPermissionGroup; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_UNTRUSTALL_GROUP) +public class CommandUntrustGroupAll extends BaseCommand { + + @CommandCompletion("@gdgroups @gdtrusttypes @gddummy") + @CommandAlias("untrustallgroup") + @Description("Revokes group access to all your claims") + @Syntax("<group>") + @Subcommand("untrustall group") + public void execute(Player player, String target, String type) { + final GDPermissionGroup group = PermissionHolderCache.getInstance().getOrCreateGroup(target); + + // validate player argument + if (group == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_GROUP, + ImmutableMap.of( + "group", target))); + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + Set<Claim> claimList = null; + if (playerData != null) { + claimList = playerData.getInternalClaims(); + } + + if (playerData == null || claimList == null || claimList.size() == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().TRUST_NO_CLAIMS); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDGroupTrustClaimEvent.Remove + event = new GDGroupTrustClaimEvent.Remove(new ArrayList<>(claimList), ImmutableList.of(group.getName()), TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", group)))); + return; + } + + for (Claim claim : claimList) { + this.removeAllGroupTrust(claim, group); + } + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UNTRUST_INDIVIDUAL_ALL_CLAIMS, + ImmutableMap.of( + "player", group.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } + + private void removeAllGroupTrust(Claim claim, GDPermissionGroup holder) { + GDClaim gdClaim = (GDClaim) claim; + gdClaim.removeAllTrustsFromGroup(holder.getName()); + gdClaim.getInternalClaimData().setRequiresSave(true); + gdClaim.getInternalClaimData().save(); + for (Claim child : gdClaim.children) { + this.removeAllGroupTrust(child, holder); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayer.java b/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayer.java new file mode 100644 index 0000000..3db8f24 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayer.java @@ -0,0 +1,121 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_UNTRUST_PLAYER) +public class CommandUntrustPlayer extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("untrust|ut") + @Description("Revokes player access to your claim.") + @Syntax("<player>") + @Subcommand("untrust player") + public void execute(Player player, String target) { + GDPermissionUser user; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + if (user == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", target))); + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (!claim.getOwnerUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(player) != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + if (user.getUniqueId().equals(player.getUniqueId()) && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().UNTRUST_SELF); + return; + } + + if (claim.getOwnerUniqueId().equals(user.getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_OWNER_ALREADY); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDUserTrustClaimEvent.Remove + event = + new GDUserTrustClaimEvent.Remove(claim, ImmutableList.of(user.getUniqueId()), TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName())))); + return; + } + + claim.removeAllTrustsFromUser(user.getUniqueId()); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UNTRUST_INDIVIDUAL_SINGLE_CLAIM, + ImmutableMap.of( + "target", user.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayerAll.java b/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayerAll.java new file mode 100644 index 0000000..12eb951 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/CommandUntrustPlayerAll.java @@ -0,0 +1,126 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Subcommand; +import co.aikar.commands.annotation.Syntax; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageDataConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.Set; + +@CommandAlias("%griefdefender") +@CommandPermission(GDPermissions.COMMAND_UNTRUSTALL_PLAYER) +public class CommandUntrustPlayerAll extends BaseCommand { + + @CommandCompletion("@gdplayers @gddummy") + @CommandAlias("untrustall") + @Description("Revokes player access to all your claims.") + @Syntax("<player>") + @Subcommand("untrustall player") + public void execute(Player player, String target) { + GDPermissionUser user; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + // validate player argument + if (user == null) { + GriefDefenderPlugin.sendMessage(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, ImmutableMap.of( + "player", target))); + return; + } + if (user.getUniqueId().equals(player.getUniqueId())) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().UNTRUST_SELF); + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + Set<Claim> claimList = null; + if (playerData != null) { + claimList = playerData.getInternalClaims(); + } + + if (playerData == null || claimList == null || claimList.size() == 0) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().UNTRUST_NO_CLAIMS); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + GDUserTrustClaimEvent.Remove + event = new GDUserTrustClaimEvent.Remove(new ArrayList<>(claimList), ImmutableList.of(user.getUniqueId()), TrustTypes.NONE); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(player, event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName())))); + return; + } + + for (Claim claim : claimList) { + this.removeAllUserTrust(claim, user); + } + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.UNTRUST_INDIVIDUAL_ALL_CLAIMS, + ImmutableMap.of( + "player", user.getName())); + GriefDefenderPlugin.sendMessage(player, message); + } + + private void removeAllUserTrust(Claim claim, GDPermissionUser user) { + final GDClaim gdClaim = ((GDClaim) claim); + gdClaim.removeAllTrustsFromUser(user.getUniqueId()); + gdClaim.getInternalClaimData().setRequiresSave(true); + gdClaim.getInternalClaimData().save(); + for (Claim child : gdClaim.children) { + this.removeAllUserTrust(child, user); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/ComponentMessageException.java b/sponge/src/main/java/com/griefdefender/command/ComponentMessageException.java new file mode 100644 index 0000000..a097300 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/ComponentMessageException.java @@ -0,0 +1,104 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import net.kyori.text.Component; +import net.kyori.text.serializer.plain.PlainComponentSerializer; + +/** + * A subclass of Exception that contains a rich message that is an instance of + * {@link Text} rather than a String. This allows formatted and localized + * exception messages. + */ +public class ComponentMessageException extends Exception { + + private static final long serialVersionUID = -5281221645176698853L; + + @Nullable private final Component message; + + /** + * Constructs a new {@link ComponentMessageException}. + */ + public ComponentMessageException() { + this.message = null; + } + + /** + * Constructs a new {@link ComponentMessageException} with the given message. + * + * @param message The detail message + */ + public ComponentMessageException(Component message) { + this.message = message; + } + + /** + * Constructs a new {@link ComponentMessageException} with the given message and + * cause. + * + * @param message The detail message + * @param throwable The cause + */ + public ComponentMessageException(Component message, Throwable throwable) { + super(throwable); + this.message = message; + } + + /** + * Constructs a new {@link ComponentMessageException} with the given cause. + * + * @param throwable The cause + */ + public ComponentMessageException(Throwable throwable) { + super(throwable); + this.message = null; + } + + @Override + @Nullable + public String getMessage() { + Component message = getText(); + return message == null ? null : PlainComponentSerializer.INSTANCE.serialize(message); + } + + /** + * Returns the text message for this exception, or null if nothing is + * present. + * + * @return The text for this message + */ + @Nullable + public Component getText() { + return this.message; + } + + @Override + @Nullable + public String getLocalizedMessage() { + return getMessage(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/gphelper/CommandAccessTrust.java b/sponge/src/main/java/com/griefdefender/command/gphelper/CommandAccessTrust.java new file mode 100644 index 0000000..d20bf46 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/gphelper/CommandAccessTrust.java @@ -0,0 +1,125 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command.gphelper; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +public class CommandAccessTrust extends BaseCommand { + + @CommandCompletion("@gdplayers") + @CommandAlias("at|accesstrust") + @Description("Grants a player access to interact with all blocks except inventory.") + @Syntax("<player>") + public void execute(Player src, String target) { + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(src.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + GDPermissionUser user = null; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + if (user == null) { + GriefDefenderPlugin.sendMessage(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", target))); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), src.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, src.getLocation()); + if (!claim.getOwnerUniqueId().equals(src.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(src) != null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + if (user.getUniqueId().equals(src.getUniqueId()) && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().TRUST_SELF); + return; + } + + if (user != null && claim.getOwnerUniqueId().equals(user.getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_OWNER_ALREADY); + return; + } else { + //check permission here + if(claim.allowGrantPermission(src) != null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(src, message); + return; + } + } + + GDCauseStackManager.getInstance().pushCause(src); + GDUserTrustClaimEvent.Add + event = + new GDUserTrustClaimEvent.Add(claim, ImmutableList.of(user.getUniqueId()), TrustTypes.ACCESSOR); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(src, event.getMessage().orElse(event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName()))))); + return; + } + + claim.addUserTrust(user.getUniqueId(), TrustTypes.ACCESSOR); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_GRANT, ImmutableMap.of( + "target", user.getName(), + "type", TrustTypes.ACCESSOR.getName())); + GriefDefenderPlugin.sendMessage(src, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/command/gphelper/CommandContainerTrust.java b/sponge/src/main/java/com/griefdefender/command/gphelper/CommandContainerTrust.java new file mode 100644 index 0000000..92dda41 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/command/gphelper/CommandContainerTrust.java @@ -0,0 +1,125 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.command.gphelper; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDUserTrustClaimEvent; +import com.griefdefender.permission.GDPermissionUser; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.entity.living.player.Player; + +public class CommandContainerTrust extends BaseCommand { + + @CommandCompletion("@gdplayers") + @CommandAlias("ct|containertrust") + @Description("Grants a player access to interact with all blocks including inventory.") + @Syntax("<player>") + public void execute(Player src, String target) { + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(src.getWorld().getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_DISABLED_WORLD); + return; + } + + GDPermissionUser user = null; + if (target.equalsIgnoreCase("public")) { + user = GriefDefenderPlugin.PUBLIC_USER; + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(target); + } + + if (user == null) { + GriefDefenderPlugin.sendMessage(src, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.COMMAND_INVALID_PLAYER, + ImmutableMap.of( + "player", target))); + return; + } + + // determine which claim the player is standing in + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(src.getWorld(), src.getUniqueId()); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, src.getLocation()); + if (!claim.getOwnerUniqueId().equals(src.getUniqueId()) && !playerData.canIgnoreClaim(claim) && claim.allowEdit(src) != null) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PERMISSION_COMMAND_TRUST); + return; + } + + if (user.getUniqueId().equals(src.getUniqueId()) && !playerData.canIgnoreClaim(claim)) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().TRUST_SELF); + return; + } + + if (user != null && claim.getOwnerUniqueId().equals(user.getUniqueId())) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().CLAIM_OWNER_ALREADY); + return; + } else { + //check permission here + if(claim.allowGrantPermission(src) != null) { + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_TRUST, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(src, message); + return; + } + } + + GDCauseStackManager.getInstance().pushCause(src); + GDUserTrustClaimEvent.Add + event = + new GDUserTrustClaimEvent.Add(claim, ImmutableList.of(user.getUniqueId()), TrustTypes.CONTAINER); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + TextAdapter.sendComponent(src, event.getMessage().orElse(event.getMessage().orElse(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_PLUGIN_CANCEL, + ImmutableMap.of("target", user.getName()))))); + return; + } + + claim.addUserTrust(user.getUniqueId(), TrustTypes.CONTAINER); + claim.getInternalClaimData().setRequiresSave(true); + claim.getInternalClaimData().save(); + + final Component message = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TRUST_GRANT, ImmutableMap.of( + "target", user.getName(), + "type", TrustTypes.CONTAINER.getName())); + GriefDefenderPlugin.sendMessage(src, message); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/ClaimDataConfig.java b/sponge/src/main/java/com/griefdefender/configuration/ClaimDataConfig.java new file mode 100644 index 0000000..41104de --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/ClaimDataConfig.java @@ -0,0 +1,552 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.data.EconomyData; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.category.ConfigCategory; +import com.griefdefender.internal.util.BlockUtil; +import net.kyori.text.Component; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@ConfigSerializable +public class ClaimDataConfig extends ConfigCategory implements IClaimData { + + private boolean requiresSave = false; + private Vector3i lesserPos; + private Vector3i greaterPos; + private Vector3i spawnPos; + private ClaimStorageData claimStorage; + + @Setting + private UUID parent; + @Setting(value = ClaimStorageData.MAIN_INHERIT_PARENT) + public boolean inheritParent = true; + @Setting(value = ClaimStorageData.MAIN_WORLD_UUID) + private UUID worldUniqueId; + @Setting(value = ClaimStorageData.MAIN_OWNER_UUID) + private UUID ownerUniqueId; + @Setting(value = ClaimStorageData.MAIN_CLAIM_TYPE) + private ClaimType claimType = ClaimTypes.BASIC; + @Setting(value = ClaimStorageData.MAIN_CLAIM_TYPE) + private ClaimType customClaimType; + @Setting(value = ClaimStorageData.MAIN_CLAIM_CUBOID) + private boolean isCuboid = false; + @Setting(value = ClaimStorageData.MAIN_CLAIM_RESIZABLE) + private boolean isResizable = true; + @Setting + private boolean isExpired = false; + @Setting + private boolean sizeRestrictions = true; + @Setting(value = ClaimStorageData.MAIN_ALLOW_DENY_MESSAGES) + private boolean allowDenyMessages = true; + @Setting(value = ClaimStorageData.MAIN_ALLOW_CLAIM_EXPIRATION) + private boolean allowClaimExpiration = true; + @Setting(value = ClaimStorageData.MAIN_ALLOW_FLAG_OVERRIDES) + private boolean allowFlagOverrides = true; + @Setting(value = ClaimStorageData.MAIN_REQUIRES_CLAIM_BLOCKS) + private boolean requiresClaimBlocks = true; + @Setting(value = ClaimStorageData.MAIN_CLAIM_PVP) + private Tristate pvpOverride = Tristate.UNDEFINED; + @Setting(value = ClaimStorageData.MAIN_CLAIM_DATE_CREATED) + private String dateCreated = Instant.now().toString(); + @Setting(value = ClaimStorageData.MAIN_CLAIM_DATE_LAST_ACTIVE) + private String dateLastActive = Instant.now().toString(); + @Setting(value = ClaimStorageData.MAIN_CLAIM_NAME) + private Component claimName; + @Setting(value = ClaimStorageData.MAIN_CLAIM_GREETING) + private Component claimGreetingMessage; + @Setting(value = ClaimStorageData.MAIN_CLAIM_FAREWELL) + private Component claimFarewellMessage; + @Setting(value = ClaimStorageData.MAIN_CLAIM_SPAWN) + private String claimSpawn; + @Setting(value = ClaimStorageData.MAIN_LESSER_BOUNDARY_CORNER) + private String lesserBoundaryCornerPos; + @Setting(value = ClaimStorageData.MAIN_GREATER_BOUNDARY_CORNER) + private String greaterBoundaryCornerPos; + @Setting(value = ClaimStorageData.MAIN_ACCESSORS) + private List<UUID> accessors = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_BUILDERS) + private List<UUID> builders = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_CONTAINERS) + private List<UUID> containers = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_MANAGERS) + private List<UUID> managers = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_ACCESSOR_GROUPS) + private List<String> accessorGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_BUILDER_GROUPS) + private List<String> builderGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_CONTAINER_GROUPS) + private List<String> containerGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_MANAGER_GROUPS) + private List<String> managerGroups = new ArrayList<>(); + @Setting + private EconomyDataConfig economyData = new EconomyDataConfig(); + + public ClaimDataConfig() { + + } + + public ClaimDataConfig(GDClaim claim) { + this.lesserBoundaryCornerPos = BlockUtil.getInstance().posToString(claim.lesserBoundaryCorner); + this.greaterBoundaryCornerPos = BlockUtil.getInstance().posToString(claim.greaterBoundaryCorner); + this.isCuboid = claim.cuboid; + this.claimType = claim.getType(); + //this.customClaimType = claim.getCustomType(); + this.ownerUniqueId = claim.getOwnerUniqueId(); + } + + /*public ClaimDataConfig(LinkedHashMap<String, Object> dataMap) { + for (Map.Entry<String, Object> mapEntry : dataMap.entrySet()) { + if (mapEntry.getKey().equals("world-uuid")) { + this.worldUniqueId = (UUID) UUID.fromString((String) mapEntry.getValue()); + } else if (mapEntry.getKey().equals("owner-uuid")) { + this.ownerUniqueId = (UUID) UUID.fromString((String) mapEntry.getValue()); + } else if (mapEntry.getKey().equals("claim-type")) { + String value = (String) mapEntry.getValue(); + if (!value.contains(":")) { + value = "griefdefender:" + value; + } + this.claimType = ClaimTypeRegistryModule.getInstance().getById(value).orElse(ClaimTypes.BASIC); + } else if (mapEntry.getKey().equals("cuboid")) { + this.isCuboid = (boolean) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("claim-expiration")) { + this.allowClaimExpiration = (boolean) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("date-created")) { + this.dateCreated = (String) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("date-last-active")) { + this.dateLastActive = (String) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("deny-messages")) { + this.allowDenyMessages = (boolean) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("flag-overrides")) { + this.allowFlagOverrides = (boolean) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("greater-boundary-corner")) { + this.greaterBoundaryCornerPos = (String) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("lesser-boundary-corner")) { + this.lesserBoundaryCornerPos = (String) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("pvp")) { + this.pvpOverride = Tristate.valueOf((String) mapEntry.getValue()); + } else if (mapEntry.getKey().equals("resizeable")) { + this.isResizable = (boolean) mapEntry.getValue(); + } else if (mapEntry.getKey().equals("accessors")) { + List<String> stringList = (List<String>) mapEntry.getValue(); + if (stringList != null) { + for (String str : stringList) { + this.accessors.add(UUID.fromString(str)); + } + } + } else if (mapEntry.getKey().equals("builders")) { + List<String> stringList = (List<String>) mapEntry.getValue(); + if (stringList != null) { + for (String str : stringList) { + this.builders.add(UUID.fromString(str)); + } + } + } else if (mapEntry.getKey().equals("containers")) { + List<String> stringList = (List<String>) mapEntry.getValue(); + if (stringList != null) { + for (String str : stringList) { + this.containers.add(UUID.fromString(str)); + } + } + } else if (mapEntry.getKey().equals("managers")) { + List<String> stringList = (List<String>) mapEntry.getValue(); + if (stringList != null) { + for (String str : stringList) { + this.managers.add(UUID.fromString(str)); + } + } + } + } + }*/ + + @Override + public UUID getWorldUniqueId() { + return this.worldUniqueId; + } + + @Override + public UUID getOwnerUniqueId() { + return this.ownerUniqueId; + } + + @Override + public boolean allowExpiration() { + return this.allowClaimExpiration; + } + + @Override + public boolean allowFlagOverrides() { + return this.allowFlagOverrides; + } + + @Override + public boolean isCuboid() { + return this.isCuboid; + } + + @Override + public boolean allowDenyMessages() { + return this.allowDenyMessages; + } + + @Override + public Tristate getPvpOverride() { + return this.pvpOverride; + } + + @Override + public boolean isResizable() { + return this.isResizable; + } + + @Override + public boolean hasSizeRestrictions() { + if (this.claimType == ClaimTypes.ADMIN || this.claimType == ClaimTypes.WILDERNESS) { + this.sizeRestrictions = false; + return false; + } + return this.sizeRestrictions; + } + + @Override + public ClaimType getType() { + return this.claimType; + } + + /*@Override + public ClaimType getCustomType() { + return this.customClaimType; + }*/ + + @Override + public Instant getDateCreated() { + return Instant.parse(this.dateCreated); + } + + @Override + public Instant getDateLastActive() { + return Instant.parse(this.dateLastActive); + } + + @Override + public Optional<Component> getName() { + return Optional.ofNullable(this.claimName); + } + + @Override + public Optional<Component> getGreeting() { + return Optional.ofNullable(this.claimGreetingMessage); + } + + @Override + public Optional<Component> getFarewell() { + return Optional.ofNullable(this.claimFarewellMessage); + } + + @Override + public Optional<Vector3i> getSpawnPos() { + if (this.spawnPos == null && this.claimSpawn != null) { + try { + this.spawnPos = BlockUtil.getInstance().posFromString(this.claimSpawn); + this.requiresSave = true; + } catch (Exception e) { + e.printStackTrace(); + } + } + + return Optional.ofNullable(this.spawnPos); + } + + @Override + public Vector3i getLesserBoundaryCornerPos() { + if (this.lesserPos == null) { + try { + this.lesserPos = BlockUtil.getInstance().posFromString(this.lesserBoundaryCornerPos); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return this.lesserPos; + } + + @Override + public Vector3i getGreaterBoundaryCornerPos() { + if (this.greaterPos == null) { + try { + this.greaterPos = BlockUtil.getInstance().posFromString(this.greaterBoundaryCornerPos); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return this.greaterPos; + } + + public List<UUID> getAccessors() { + return this.accessors; + } + + public List<UUID> getBuilders() { + return this.builders; + } + + public List<UUID> getContainers() { + return this.containers; + } + + public List<UUID> getManagers() { + return this.managers; + } + + public List<String> getAccessorGroups() { + return this.accessorGroups; + } + + public List<String> getBuilderGroups() { + return this.builderGroups; + } + + public List<String> getContainerGroups() { + return this.containerGroups; + } + + public List<String> getManagerGroups() { + return this.managerGroups; + } + + @Override + public void setDenyMessages(boolean flag) { + this.requiresSave = true; + this.allowDenyMessages = flag; + } + + @Override + public void setExpiration(boolean flag) { + this.requiresSave = true; + this.allowClaimExpiration = flag; + } + + @Override + public void setFlagOverrides(boolean flag) { + this.allowFlagOverrides = flag; + } + + @Override + public void setCuboid(boolean cuboid) { + this.isCuboid = cuboid; + } + + @Override + public void setPvpOverride(Tristate pvp) { + this.requiresSave = true; + this.pvpOverride = pvp; + } + + @Override + public void setResizable(boolean resizable) { + this.requiresSave = true; + this.isResizable = resizable; + } + + @Override + public void setType(ClaimType type) { + this.requiresSave = true; + this.claimType = type; + } + + /*@Override + public void setCustomType(ClaimType type) { + this.requiresSave = true; + this.customClaimType = type; + }*/ + + @Override + public void setDateLastActive(Instant date) { + this.requiresSave = true; + this.dateLastActive = date.toString(); + } + + @Override + public void setName(Component name) { + this.requiresSave = true; + this.claimName = name; + } + + @Override + public void setGreeting(Component message) { + this.requiresSave = true; + this.claimGreetingMessage = message; + } + + @Override + public void setFarewell(Component message) { + this.requiresSave = true; + this.claimFarewellMessage = message; + } + + @Override + public void setLesserBoundaryCorner(String location) { + this.requiresSave = true; + this.lesserBoundaryCornerPos = location; + this.lesserPos = null; + } + + @Override + public void setGreaterBoundaryCorner(String location) { + this.requiresSave = true; + this.greaterBoundaryCornerPos = location; + this.greaterPos = null; + } + + @Override + public void setAccessors(List<UUID> accessors) { + this.requiresSave = true; + this.accessors = accessors; + } + + @Override + public void setBuilders(List<UUID> builders) { + this.requiresSave = true; + this.builders = builders; + } + + @Override + public void setContainers(List<UUID> containers) { + this.requiresSave = true; + this.containers = containers; + } + + @Override + public void setManagers(List<UUID> coowners) { + this.requiresSave = true; + this.managers = coowners; + } + + public boolean requiresSave() { + return this.requiresSave; + } + + @Override + public void setRequiresSave(boolean flag) { + this.requiresSave = flag; + } + + @Override + public void setSizeRestrictions(boolean sizeRestrictions) { + this.sizeRestrictions = sizeRestrictions; + } + + @Override + public boolean doesInheritParent() { + // NOTE: admin claims ONLY inherit from other parent admin claims + return this.inheritParent; + } + + @Override + public void setInheritParent(boolean flag) { + this.requiresSave = true; + this.inheritParent = flag; + } + + @Override + public void setOwnerUniqueId(UUID newClaimOwner) { + this.requiresSave = true; + this.ownerUniqueId = newClaimOwner; + } + + @Override + public void setWorldUniqueId(UUID uuid) { + this.requiresSave = true; + this.worldUniqueId = uuid; + } + + public void setClaimStorageData(ClaimStorageData claimStorage) { + this.claimStorage = claimStorage; + } + + @Override + public void save() { + this.claimStorage.save(); + } + + @Override + public void setSpawnPos(Vector3i spawnPos) { + if (spawnPos == null) { + return; + } + + this.requiresSave = true; + this.spawnPos = spawnPos; + this.claimSpawn = BlockUtil.getInstance().posToString(spawnPos); + } + + @Override + public boolean requiresClaimBlocks() { + return this.requiresClaimBlocks; + } + + @Override + public void setRequiresClaimBlocks(boolean requiresClaimBlocks) { + this.requiresSave = true; + this.requiresClaimBlocks = requiresClaimBlocks; + } + + @Override + public void setParent(UUID uuid) { + this.requiresSave = true; + this.parent = uuid; + } + + @Override + public Optional<UUID> getParent() { + return Optional.ofNullable(this.parent); + } + + public boolean isExpired() { + return this.isExpired; + } + + public void setExpired(boolean expire) { + this.isExpired = expire; + } + + @Override + public EconomyData getEconomyData() { + return this.economyData; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/ClaimStorageData.java b/sponge/src/main/java/com/griefdefender/configuration/ClaimStorageData.java new file mode 100644 index 0000000..303238e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/ClaimStorageData.java @@ -0,0 +1,209 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMapper; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers; +import org.spongepowered.api.Sponge; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class ClaimStorageData { + + protected HoconConfigurationLoader loader; + private CommentedConfigurationNode root = SimpleCommentedConfigurationNode.root(ConfigurationOptions.defaults()); + protected ObjectMapper<ClaimDataConfig>.BoundInstance configMapper; + protected ClaimDataConfig configBase; + public Path filePath; + public Path folderPath; + + // MAIN + public static final String MAIN_WORLD_UUID = "world-uuid"; + public static final String MAIN_OWNER_UUID = "owner-uuid"; + public static final String MAIN_CLAIM_NAME = "claim-name"; + public static final String MAIN_CLAIM_GREETING = "claim-greeting"; + public static final String MAIN_CLAIM_FAREWELL = "claim-farewell"; + public static final String MAIN_CLAIM_SPAWN = "claim-spawn"; + public static final String MAIN_CLAIM_TYPE = "claim-type"; + public static final String MAIN_CLAIM_CUSTOM_TYPE = "claim-type-custom"; + public static final String MAIN_CLAIM_CUBOID = "cuboid"; + public static final String MAIN_CLAIM_RESIZABLE = "resizable"; + public static final String MAIN_CLAIM_PVP = "pvp"; + public static final String MAIN_CLAIM_DATE_CREATED = "date-created"; + public static final String MAIN_CLAIM_DATE_LAST_ACTIVE = "date-last-active"; + public static final String MAIN_CLAIM_MAX_WIDTH = "max-width"; + public static final String MAIN_CLAIM_FOR_SALE = "for-sale"; + public static final String MAIN_CLAIM_SALE_PRICE = "sale-price"; + public static final String MAIN_REQUIRES_CLAIM_BLOCKS = "requires-claim-blocks"; + public static final String MAIN_SUBDIVISION_UUID = "uuid"; + public static final String MAIN_PARENT_CLAIM_UUID = "parent-claim-uuid"; + public static final String MAIN_LESSER_BOUNDARY_CORNER = "lesser-boundary-corner"; + public static final String MAIN_GREATER_BOUNDARY_CORNER = "greater-boundary-corner"; + public static final String MAIN_ACCESSORS = "accessors"; + public static final String MAIN_BUILDERS = "builders"; + public static final String MAIN_CONTAINERS = "containers"; + public static final String MAIN_MANAGERS = "managers"; + public static final String MAIN_ACCESSOR_GROUPS = "accessor-groups"; + public static final String MAIN_BUILDER_GROUPS = "builder-groups"; + public static final String MAIN_CONTAINER_GROUPS = "container-groups"; + public static final String MAIN_MANAGER_GROUPS = "manager-groups"; + public static final String MAIN_ALLOW_DENY_MESSAGES = "deny-messages"; + public static final String MAIN_ALLOW_FLAG_OVERRIDES = "flag-overrides"; + public static final String MAIN_ALLOW_CLAIM_EXPIRATION = "claim-expiration"; + public static final String MAIN_TAX_PAST_DUE_DATE = "tax-past-due-date"; + public static final String MAIN_TAX_BALANCE = "tax-balance"; + // SUB + public static final String MAIN_INHERIT_PARENT = "inherit-parent"; + + // Used for new claims after server startup + @SuppressWarnings({"unchecked", "rawtypes"}) + public ClaimStorageData(Path path, UUID worldUniqueId, UUID ownerUniqueId, ClaimType type, boolean cuboid) { + this.filePath = path; + this.folderPath = path.getParent(); + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + if (type == ClaimTypes.TOWN) { + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(TownDataConfig.class).bindToNew(); + } else { + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(ClaimDataConfig.class).bindToNew(); + } + this.configMapper.getInstance().setWorldUniqueId(worldUniqueId); + this.configMapper.getInstance().setOwnerUniqueId(ownerUniqueId); + this.configMapper.getInstance().setType(type); + this.configMapper.getInstance().setCuboid(cuboid); + this.configMapper.getInstance().setClaimStorageData(this); + reload(); + ((EconomyDataConfig) this.configMapper.getInstance().getEconomyData()).activeConfig = GriefDefenderPlugin.getActiveConfig(Sponge.getServer().getWorld(worldUniqueId).get().getProperties()); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize configuration", e); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public ClaimStorageData(Path path, UUID worldUniqueId, ClaimDataConfig claimData) { + this.filePath = path; + this.folderPath = path.getParent(); + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(ClaimDataConfig.class).bind(claimData); + this.configMapper.getInstance().setClaimStorageData(this); + reload(); + ((EconomyDataConfig) this.configMapper.getInstance().getEconomyData()).activeConfig = GriefDefenderPlugin.getActiveConfig(worldUniqueId); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize configuration", e); + } + } + + // Used during server load + @SuppressWarnings({"unchecked", "rawtypes"}) + public ClaimStorageData(Path path, UUID worldUniqueId) { + this.filePath = path; + this.folderPath = path.getParent(); + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + if (path.getParent().endsWith("town")) { + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(TownDataConfig.class).bindToNew(); + } else { + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(ClaimDataConfig.class).bindToNew(); + } + this.configMapper.getInstance().setClaimStorageData(this); + try { + this.root = this.loader.load(ConfigurationOptions.defaults()); + CommentedConfigurationNode rootNode = this.root.getNode(GriefDefenderPlugin.MOD_ID); + // Check if server is using existing Sponge GP data + if (rootNode.isVirtual()) { + // check GriefPrevention + CommentedConfigurationNode gpRootNode = this.root.getNode("GriefPrevention"); + if (!gpRootNode.isVirtual()) { + rootNode.setValue(gpRootNode.getValue()); + gpRootNode.setValue(null); + } + } + this.configBase = this.configMapper.populate(rootNode); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration", e); + } + ((EconomyDataConfig) this.configMapper.getInstance().getEconomyData()).activeConfig = GriefDefenderPlugin.getActiveConfig(worldUniqueId); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize configuration", e); + } + } + + public ClaimDataConfig getConfig() { + return this.configBase; + } + + public void save() { + try { + this.configMapper.serialize(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + this.loader.save(this.root); + this.configBase.setRequiresSave(false); + } catch (IOException | ObjectMappingException e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to save configuration", e); + } + } + + public void reload() { + try { + this.root = this.loader.load(ConfigurationOptions.defaults()); + this.configBase = this.configMapper.populate(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration", e); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateConfig.java b/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateConfig.java new file mode 100644 index 0000000..a8fc800 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateConfig.java @@ -0,0 +1,130 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.google.common.collect.Maps; +import com.griefdefender.api.permission.Context; +import com.griefdefender.configuration.category.ConfigCategory; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@ConfigSerializable +public class ClaimTemplateConfig extends ConfigCategory { + + @Setting(value = "name", comment = "The template name.") + public String templateName; + @Setting(value = "description", comment = "A description to help describe the template.") + public String templateDescription = ""; + @Setting(value = ClaimStorageData.MAIN_OWNER_UUID, comment = "The owner uuid that created this template.") + public UUID ownerUniqueId; + @Setting(value = ClaimStorageData.MAIN_CLAIM_DATE_CREATED, comment = "The date and time this template was created.") + public String dateCreated = Instant.now().toString(); + @Setting(value = ClaimStorageData.MAIN_ACCESSORS, comment = "The accessors associated with subdivision.") + public ArrayList<UUID> accessors = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_BUILDERS, comment = "The builders associated with subdivision.") + public ArrayList<UUID> builders = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_CONTAINERS, comment = "The containers associated with subdivision.") + public ArrayList<UUID> containers = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_MANAGERS, comment = "The coowners associated with subdivision.") + public ArrayList<UUID> coowners = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_ACCESSOR_GROUPS) + private List<String> accessorGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_BUILDER_GROUPS) + private List<String> builderGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_CONTAINER_GROUPS) + private List<String> containerGroups = new ArrayList<>(); + @Setting(value = ClaimStorageData.MAIN_MANAGER_GROUPS) + private List<String> managerGroups = new ArrayList<>(); + @Setting(value = "permissions") + private Map<Context, String> permissions = Maps.newHashMap(); + + public ClaimTemplateConfig() { + + } + + public UUID getOwnerUniqueId() { + return this.ownerUniqueId; + } + + public String getDateCreated() { + return this.dateCreated; + } + + public List<UUID> getAccessors() { + return this.accessors; + } + + + public List<UUID> getBuilders() { + return this.builders; + } + + + public List<UUID> getContainers() { + return this.containers; + } + + public List<UUID> getCoowners() { + return this.coowners; + } + + public List<String> getAccessorGroups() { + return this.accessorGroups; + } + + public List<String> getBuilderGroups() { + return this.builderGroups; + } + + public List<String> getContainerGroups() { + return this.containerGroups; + } + + public List<String> getManagerGroups() { + return this.managerGroups; + } + + public String getTemplateName() { + return this.templateName; + } + + public void setTemplateName(String template) { + this.templateName = template; + } + + public String getTemplateDescription() { + return this.templateDescription; + } + + public void setTemplateDescription(String description) { + this.templateDescription = description; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateStorage.java b/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateStorage.java new file mode 100644 index 0000000..bfccf0e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/ClaimTemplateStorage.java @@ -0,0 +1,133 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.storage.FileStorage; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMapper; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Optional; +import java.util.UUID; + +public class ClaimTemplateStorage { + + private HoconConfigurationLoader loader; + private CommentedConfigurationNode root = SimpleCommentedConfigurationNode.root(ConfigurationOptions.defaults() + .setHeader(GriefDefenderPlugin.CONFIG_HEADER)); + private ObjectMapper<ClaimTemplateConfig>.BoundInstance configMapper; + private ClaimTemplateConfig configBase; + public Path filePath; + + @SuppressWarnings({"unchecked", "rawtypes"}) + public ClaimTemplateStorage(Path path) { + this.filePath = path; + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(ClaimTemplateConfig.class).bindToNew(); + + if (reload()) { + save(); + } + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize claim template data", e); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public ClaimTemplateStorage(String templateName, Optional<String> description, IClaimData claimData, UUID creator) { + this.filePath = FileStorage.rootWorldSavePath.resolve(FileStorage.claimTemplatePath.resolve(UUID.randomUUID().toString())); + try { + if (Files.notExists(this.filePath.getParent())) { + Files.createDirectories(this.filePath.getParent()); + } + if (Files.notExists(this.filePath)) { + Files.createFile(this.filePath); + } + + this.loader = HoconConfigurationLoader.builder().setPath(this.filePath).build(); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(ClaimTemplateConfig.class).bindToNew(); + + reload(); + this.configBase.templateName = templateName; + if (description.isPresent()) { + this.configBase.templateDescription = description.get(); + } + this.configBase.ownerUniqueId = creator; + this.configBase.accessors = new ArrayList<UUID>(claimData.getAccessors()); + this.configBase.builders = new ArrayList<UUID>(claimData.getBuilders()); + this.configBase.containers = new ArrayList<UUID>(claimData.getContainers()); + this.configBase.coowners = new ArrayList<UUID>(claimData.getManagers()); + //this.configBase.flags = new HashMap<String, Tristate>(claimData.getFlags()); + save(); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize claim template data", e); + } + } + + public ClaimTemplateConfig getConfig() { + return this.configBase; + } + + public void save() { + try { + this.configMapper.serialize(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + this.loader.save(this.root); + } catch (IOException | ObjectMappingException e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to save configuration", e); + } + } + + public boolean reload() { + try { + this.root = this.loader.load(ConfigurationOptions.defaults() + .setHeader(GriefDefenderPlugin.CONFIG_HEADER)); + this.configBase = this.configMapper.populate(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration", e); + return false; + } + return true; + } + + public CommentedConfigurationNode getRootNode() { + return this.root.getNode(GriefDefenderPlugin.MOD_ID); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/EconomyDataConfig.java b/sponge/src/main/java/com/griefdefender/configuration/EconomyDataConfig.java new file mode 100644 index 0000000..cc07510 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/EconomyDataConfig.java @@ -0,0 +1,120 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.google.gson.Gson; +import com.griefdefender.api.data.EconomyData; +import com.griefdefender.api.economy.BankTransaction; +import com.griefdefender.configuration.category.ConfigCategory; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@ConfigSerializable +public class EconomyDataConfig extends ConfigCategory implements EconomyData { + + public GriefDefenderConfig<?> activeConfig; + + @Setting(value = ClaimStorageData.MAIN_CLAIM_FOR_SALE) + private boolean forSale = false; + @Setting(value = ClaimStorageData.MAIN_CLAIM_SALE_PRICE) + private double salePrice = 0.0; + @Setting(value = ClaimStorageData.MAIN_TAX_BALANCE) + private double taxBalance = 0.0; + @Setting(value = ClaimStorageData.MAIN_TAX_PAST_DUE_DATE) + private String taxPastDueDate; + @Setting + private List<String> bankTransactionLog = new ArrayList<>(); + + @Override + public boolean isForSale() { + return this.forSale; + } + + @Override + public double getSalePrice() { + return this.salePrice; + } + + @Override + public void setForSale(boolean forSale) { + this.forSale = forSale; + } + + @Override + public double getTaxBalance() { + return this.taxBalance; + } + + @Override + public void setTaxBalance(double balance) { + this.taxBalance = balance; + } + + @Override + public Optional<Instant> getTaxPastDueDate() { + if (this.taxPastDueDate == null) { + return Optional.empty(); + } + try { + return Optional.of(Instant.parse(this.taxPastDueDate)); + } catch (DateTimeParseException e) { + return Optional.empty(); + } + } + + @Override + public void setTaxPastDueDate(Instant date) { + this.taxPastDueDate = date == null ? null : date.toString(); + } + + @Override + public void setSalePrice(double price) { + this.salePrice = price; + } + + @Override + public List<String> getBankTransactionLog() { + return this.bankTransactionLog; + } + + @Override + public void addBankTransaction(BankTransaction transaction) { + if (this.getBankTransactionLog().size() == this.activeConfig.getConfig().claim.bankTransactionLogLimit) { + this.getBankTransactionLog().remove(0); + } + this.getBankTransactionLog().add(new Gson().toJson(transaction)); + } + + @Override + public void clearBankTransactionLog() { + this.bankTransactionLog.clear(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/GriefDefenderConfig.java b/sponge/src/main/java/com/griefdefender/configuration/GriefDefenderConfig.java new file mode 100644 index 0000000..65deaf4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/GriefDefenderConfig.java @@ -0,0 +1,230 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.configuration.type.ConfigBase; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.Types; +import ninja.leaping.configurate.ValueType; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMapper; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.util.ConfigurationNodeWalker; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +public class GriefDefenderConfig<T extends ConfigBase> { + + private static final ConfigurationOptions LOADER_OPTIONS = ConfigurationOptions.defaults() + .setHeader(GriefDefenderPlugin.CONFIG_HEADER); + + private final Path path; + + /** + * The parent configuration - values are inherited from this + */ + private final GriefDefenderConfig<?> parent; + + /** + * The loader (mapped to a file) used to read/write the config to disk + */ + private HoconConfigurationLoader loader; + + /** + * A node representation of "whats actually in the file". + */ + private CommentedConfigurationNode fileData = SimpleCommentedConfigurationNode.root(LOADER_OPTIONS); + + /** + * A node representation of {@link #fileData}, merged with the data of {@link #parent}. + */ + private CommentedConfigurationNode data = SimpleCommentedConfigurationNode.root(LOADER_OPTIONS); + + /** + * The mapper instance used to populate the config instance + */ + private ObjectMapper<T>.BoundInstance configMapper; + + public GriefDefenderConfig(Class<T> clazz, Path path, GriefDefenderConfig<?> parent) { + this.parent = parent; + this.path = path; + + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + this.configMapper = ObjectMapper.forClass(clazz).bindToNew(); + + load(); + // In order for the removeDuplicates method to function properly, it is extremely + // important to avoid running save on parent BEFORE children save. Doing so will + // cause duplicate nodes to not be removed properly as parent would have cleaned up + // all duplicates prior. + // To handle the above issue, we only call save for world configs during init. + if (parent != null && parent.parent != null) { + save(); + } + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration at path " + path.toAbsolutePath()); + e.printStackTrace(); + } + } + + public T getConfig() { + return this.configMapper.getInstance(); + } + + public boolean save() { + try { + // save from the mapped object --> node + CommentedConfigurationNode saveNode = SimpleCommentedConfigurationNode.root(LOADER_OPTIONS); + this.configMapper.serialize(saveNode.getNode(GriefDefenderPlugin.MOD_ID)); + + // before saving this config, remove any values already declared with the same value on the parent + if (this.parent != null) { + removeDuplicates(saveNode); + } + + // save the data to disk + this.loader.save(saveNode); + + // In order for the removeDuplicates method to function properly, it is extremely + // important to avoid running save on parent BEFORE children save. Doing so will + // cause duplicate nodes to not be removed as parent would have cleaned up + // all duplicates prior. + // To handle the above issue, we save AFTER saving child config. + if (this.parent != null) { + this.parent.save(); + } + return true; + } catch (IOException | ObjectMappingException e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to save configuration"); + e.printStackTrace(); + return false; + } + } + + public void load() throws IOException, ObjectMappingException { + // load settings from file + CommentedConfigurationNode loadedNode = this.loader.load(); + + // store "what's in the file" separately in memory + this.fileData = loadedNode; + + // make a copy of the file data + this.data = this.fileData.copy(); + + // merge with settings from parent + if (this.parent != null) { + this.parent.load(); + this.data.mergeValuesFrom(this.parent.data); + } + + // populate the config object + populateInstance(); + } + + private void populateInstance() throws ObjectMappingException { + this.configMapper.populate(this.data.getNode(GriefDefenderPlugin.MOD_ID)); + } + + /** + * Traverses the given {@code root} config node, removing any values which + * are also present and set to the same value on this configs "parent". + * + * @param root The node to process + */ + private void removeDuplicates(CommentedConfigurationNode root) { + if (this.parent == null) { + throw new IllegalStateException("parent is null"); + } + + Iterator<ConfigurationNodeWalker.VisitedNode<CommentedConfigurationNode>> it = ConfigurationNodeWalker.DEPTH_FIRST_POST_ORDER.walkWithPath(root); + while (it.hasNext()) { + ConfigurationNodeWalker.VisitedNode<CommentedConfigurationNode> next = it.next(); + CommentedConfigurationNode node = next.getNode(); + + // remove empty maps + if (node.hasMapChildren()) { + if (node.getChildrenMap().isEmpty()) { + node.setValue(null); + } + continue; + } + + // ignore list values + if (node.getParent() != null && node.getParent().getValueType() == ValueType.LIST) { + continue; + } + + // if the node already exists in the parent config, remove it + CommentedConfigurationNode parentValue = this.parent.data.getNode(next.getPath().getArray()); + if (Objects.equals(node.getValue(), parentValue.getValue())) { + node.setValue(null); + } else { + // Fix list bug + if (parentValue.getValue() == null) { + if (node.getValueType() == ValueType.LIST) { + final List<?> nodeList = (List<?>) node.getValue(); + if (nodeList.isEmpty()) { + node.setValue(null); + } + continue; + } + } + // Fix double bug + final Double nodeVal = node.getValue(Types::asDouble); + if (nodeVal != null) { + Double parentVal = parentValue.getValue(Types::asDouble); + if (parentVal == null && nodeVal.doubleValue() == 0 || (parentVal != null && nodeVal.doubleValue() == parentVal.doubleValue())) { + node.setValue(null); + continue; + } + } + } + } + } + + public CommentedConfigurationNode getRootNode() { + return this.data.getNode(GriefDefenderPlugin.MOD_ID); + } + + public Path getPath() { + return this.path; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/IClaimData.java b/sponge/src/main/java/com/griefdefender/configuration/IClaimData.java new file mode 100644 index 0000000..dfe7c88 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/IClaimData.java @@ -0,0 +1,80 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.data.ClaimData; + +import java.util.List; +import java.util.UUID; + +public interface IClaimData extends ClaimData { + + boolean requiresSave(); + + boolean isExpired(); + + List<UUID> getAccessors(); + + List<UUID> getBuilders(); + + List<UUID> getContainers(); + + List<UUID> getManagers(); + + List<String> getAccessorGroups(); + + List<String> getBuilderGroups(); + + List<String> getContainerGroups(); + + List<String> getManagerGroups(); + + void setOwnerUniqueId(UUID newClaimOwner); + + void setWorldUniqueId(UUID uuid); + + void setType(ClaimType type); + + //void setCustomType(ClaimType type); + + void setCuboid(boolean cuboid); + + void setLesserBoundaryCorner(String location); + + void setGreaterBoundaryCorner(String location); + + void setAccessors(List<UUID> accessors); + + void setBuilders(List<UUID> builders); + + void setContainers(List<UUID> containers); + + void setManagers(List<UUID> coowners); + + void setRequiresSave(boolean flag); + + void setExpired(boolean expire); +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/MessageDataConfig.java b/sponge/src/main/java/com/griefdefender/configuration/MessageDataConfig.java new file mode 100644 index 0000000..9d75edf --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/MessageDataConfig.java @@ -0,0 +1,68 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.google.common.collect.ImmutableMap; +import com.griefdefender.configuration.category.ConfigCategory; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.HashMap; +import java.util.Map; + +@ConfigSerializable +public class MessageDataConfig extends ConfigCategory { + + @Setting("descriptions") + public Map<String, String> descriptionMap = new HashMap<>(); + + @Setting("messages") + public Map<String, String> messageMap = new HashMap<>(); + + public Component getMessage(String message) { + return this.getMessage(message, ImmutableMap.of()); + } + + public Component getMessage(String message, Map<String, Object> paramMap) { + String rawMessage = this.messageMap.get(message); + if (rawMessage == null) { + // Should never happen but in case it does, return empty + return TextComponent.empty(); + } + for (Map.Entry<String, Object> entry : paramMap.entrySet()) { + final String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Component) { + value = LegacyComponentSerializer.legacy().serialize((Component) value, '&'); + } + rawMessage = rawMessage.replace("{" + key + "}", value.toString()); + } + + return LegacyComponentSerializer.legacy().deserialize(rawMessage, '&'); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/MessageStorage.java b/sponge/src/main/java/com/griefdefender/configuration/MessageStorage.java new file mode 100644 index 0000000..bd9db30 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/MessageStorage.java @@ -0,0 +1,380 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.GriefDefenderPlugin; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMapper; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +public class MessageStorage { + + private HoconConfigurationLoader loader; + private CommentedConfigurationNode root = SimpleCommentedConfigurationNode.root(ConfigurationOptions.defaults()); + private ObjectMapper<MessageDataConfig>.BoundInstance configMapper; + private MessageDataConfig configBase; + public static MessageDataConfig MESSAGE_DATA; + + // descriptions + public static String DESCRIPTION_ABANDON_ALL = "abandon-all"; + public static String DESCRIPTION_ABANDON_CLAIM = "abandon-claim"; + public static String DESCRIPTION_ABANDON_TOP = "abandon-top"; + public static String DESCRIPTION_BUY_BLOCKS = "buy-blocks"; + public static String DESCRIPTION_CALLBACK = "callback"; + public static String DESCRIPTION_CLAIM_BANK = "claim-bank"; + public static String DESCRIPTION_CLAIM_CLEAR = "claim-clear"; + public static String DESCRIPTION_CLAIM_DEBUG = "claim-debug"; + public static String DESCRIPTION_CLAIM_FAREWELL = "claim-farewell"; + public static String DESCRIPTION_CLAIM_GREETING = "claim-greeting"; + public static String DESCRIPTION_CLAIM_IGNORE = "claim-ignore"; + public static String DESCRIPTION_CLAIM_INFO = "claim-info"; + public static String DESCRIPTION_CLAIM_INHERIT = "claim-inherit"; + public static String DESCRIPTION_CLAIM_LIST = "claim-list"; + public static String DESCRIPTION_CLAIM_NAME = "claim-name"; + public static String DESCRIPTION_CLAIM_RESTORE = "claim-restore"; + public static String DESCRIPTION_CLAIM_SETSPAWN = "claim-setspawn"; + public static String DESCRIPTION_CLAIM_SPAWN = "claim-spawn"; + public static String DESCRIPTION_CLAIM_TRANSFER = "claim-transfer"; + public static String DESCRIPTION_CLAIM_WORLDEDIT = "claim-worldedit"; + public static String DESCRIPTION_CUBOID = "cuboid"; + public static String DESCRIPTION_DEBUG = "debug"; + public static String DESCRIPTION_DELETE_ALL = "delete-all"; + public static String DESCRIPTION_DELETE_ALL_ADMIN = "delete-all-admin"; + public static String DESCRIPTION_DELETE_CLAIM = "delete-claim"; + public static String DESCRIPTION_DELETE_TOP = "delete-top"; + public static String DESCRIPTION_FLAG_CLAIM = "flag-claim"; + public static String DESCRIPTION_FLAG_GROUP = "flag-group"; + public static String DESCRIPTION_FLAG_PLAYER = "flag-player"; + public static String DESCRIPTION_FLAG_RESET = "flag-reset"; + public static String DESCRIPTION_MODE_ADMIN = "mode-admin"; + public static String DESCRIPTION_MODE_BASIC = "mode-basic"; + public static String DESCRIPTION_MODE_NATURE = "mode-nature"; + public static String DESCRIPTION_MODE_SUBDIVISION = "mode-subdivision"; + public static String DESCRIPTION_MODE_TOWN = "mode-town"; + public static String DESCRIPTION_OPTION_CLAIM = "option-claim"; + public static String DESCRIPTION_PERMISSION_GROUP = "permission-group"; + public static String DESCRIPTION_PERMISSION_PLAYER = "permission-player"; + public static String DESCRIPTION_PLAYER_ADJUST_BONUS_BLOCKS = "player-adjust-bonus-blocks"; + public static String DESCRIPTION_PLAYER_INFO = "player-info"; + + // messages with parameters + public static final String ABANDON_CLAIM_DELAY_WARNING = "abandon-claim-delay-warning"; + public static final String ABANDON_FAILED = "abandon-failed"; + public static final String ABANDON_OTHER_SUCCESS = "abandon-other-success"; + public static final String ABANDON_SUCCESS = "abandon-success"; + public static final String ADJUST_ACCRUED_BLOCKS_SUCCESS = "adjust-accrued-blocks-success"; + public static final String ADJUST_BONUS_BLOCKS_SUCCESS = "adjust-bonus-blocks-success"; + public static final String BANK_DEPOSIT = "bank-deposit"; + public static final String BANK_INFO = "bank-info"; + public static final String BANK_NO_PERMISSION = "bank-no-permission"; + public static final String BANK_WITHDRAW = "bank-withdraw"; + public static final String BANK_WITHDRAW_NO_FUNDS = "bank-withdraw-no-funds"; + public static final String BLOCK_CLAIMED = "block-claimed"; + public static final String BLOCK_NOT_CLAIMED = "block-not-claimed"; + public static final String BLOCK_SALE_VALUE = "block-sale-value"; + public static final String CLAIM_ABOVE_LEVEL = "claim-above-level"; + public static final String CLAIM_ACTION_NOT_AVAILABLE = "claim-action-not-available"; + public static final String CLAIM_BELOW_LEVEL = "claim-below-level"; + public static final String CLAIM_CHEST_OUTSIDE_LEVEL = "claim-chest-outside-level"; + public static final String CLAIM_CONTEXT_NOT_FOUND = "claim-context-not-found"; + public static final String CLAIM_EXPIRED_INACTIVITY = "claim-expired-inactivity"; + public static final String CLAIM_FAREWELL = "claim-farewell"; + public static final String CLAIM_FAREWELL_INVALID = "claim-farewell-invalid"; + public static final String CLAIM_GREETING = "claim-greeting"; + public static final String CLAIM_LAST_ACTIVE = "claim-last-active"; + public static final String CLAIM_MODE_START = "claim-mode-start"; + public static final String CLAIM_NAME = "claim-name"; + public static final String CLAIM_OWNER_ONLY = "claim-owner-only"; + public static final String CLAIM_PROTECTED_ENTITY = "claim-protected-entity"; + public static final String CLAIM_SHOW_NEARBY = "claim-show-nearby"; + public static final String CLAIM_SIZE_MIN = "claim-size-min"; + public static final String CLAIM_SIZE_MAX = "claim-size-max"; + public static final String CLAIM_SIZE_NEED_BLOCKS_2D = "claim-size-need-blocks-2d"; + public static final String CLAIM_SIZE_NEED_BLOCKS_3D = "claim-size-need-blocks-3d"; + public static final String CLAIM_SIZE_TOO_SMALL = "size-too-small"; + public static final String CLAIM_NO_SET_HOME = "claim-no-set-home"; + public static final String CLAIM_START = "claim-start"; + public static final String CLAIM_TRANSFER_EXCEEDS_LIMIT = "claim-transfer-exceeds-limit"; + public static final String CLAIM_TRANSFER_SUCCESS = "claim-transfer-success"; + public static final String CLAIM_TYPE_NOT_FOUND = "claim-type-not-found"; + public static final String CLAIMINFO_UI_CLICK_CHANGE_CLAIM = "claiminfo-ui-click-change-claim"; + public static final String CLAIMINFO_UI_TELEPORT_DIRECTION = "claiminfo-ui-teleport-direction"; + public static final String CLAIMLIST_UI_CLICK_TELEPORT_TARGET = "claimlist-ui-click-teleport-target"; + public static final String CLAIMLIST_UI_CLICK_TOGGLE_VALUE = "claimlist-ui-click-toggle-value"; + public static final String COMMAND_BLOCKED = "command-blocked"; + public static final String COMMAND_CLAIMBAN_SUCCESS_BLOCK = "command-claimban-success-block"; + public static final String COMMAND_CLAIMBAN_SUCCESS_ENTITY = "command-claimban-success-entity"; + public static final String COMMAND_CLAIMBAN_SUCCESS_ITEM = "command-claimban-success-item"; + public static final String COMMAND_CLAIMCLEAR_NO_ENTITIES = "command-claimclear-no-entities"; + public static final String COMMAND_CLAIMNAME_NOT_FOUND = "command-claimname-not-found"; + public static final String COMMAND_CLAIMUNBAN_SUCCESS_BLOCK = "command-claimunban-success-block"; + public static final String COMMAND_CLAIMUNBAN_SUCCESS_ENTITY = "command-claimunban-success-entity"; + public static final String COMMAND_CLAIMUNBAN_SUCCESS_ITEM = "command-claimunban-success-item"; + public static final String COMMAND_EXECUTE_FAILED = "command-execute-failed"; + public static final String COMMAND_GIVEBLOCKS_CONFIRMATION = "command-giveblocks-confirmation"; + public static final String COMMAND_GIVEBLOCKS_CONFIRMED = "command-giveblocks-confirmed"; + public static final String COMMAND_GIVEBLOCKS_NOT_ENOUGH = "command-giveblocks-not-enough"; + public static final String COMMAND_GIVEBLOCKS_RECEIVED = "command-giveblocks-received"; + public static final String COMMAND_INVALID_AMOUNT = "command-invalid-amount"; + public static final String COMMAND_INVALID_CLAIM = "command-invalid-claim"; + public static final String COMMAND_INVALID_GROUP = "command-invalid-group"; + public static final String COMMAND_INVALID_PLAYER = "command-invalid-player"; + public static final String COMMAND_INVALID_TYPE = "command-invalid-type"; + public static final String COMMAND_OPTION_EXCEEDS_ADMIN = "command-option-exceeds-admin"; + public static final String COMMAND_PET_INVALID = "command-pet-invalid"; + public static final String COMMAND_PLAYER_NOT_FOUND = "command-player-not-found"; + public static final String COMMAND_WORLD_NOT_FOUND = "command-world-not-found"; + public static final String CREATE_FAILED_CLAIM_LIMIT = "create-failed-claim-limit"; + public static final String CREATE_FAILED_RESULT = "create-failed-result"; + public static final String CREATE_INSUFFICIENT_BLOCKS_2D = "create-insufficient-blocks-2d"; + public static final String CREATE_INSUFFICIENT_BLOCKS_3D = "create-insufficient-blocks-3d"; + public static final String CREATE_OVERLAP_PLAYER = "create-overlap-player"; + public static final String CREATE_SUCCESS = "create-success"; + public static final String DEBUG_ERROR_UPLOAD = "debug-error-upload"; + public static final String DELETE_ALL_TYPE_DENY = "delete-all-type-deny"; + public static final String DELETE_ALL_TYPE_SUCCESS = "delete-all-type-success"; + public static final String DELETE_ALL_TYPE_WARNING = "delete-all-type-warning"; + public static final String DELETE_ALL_PLAYER_SUCCESS = "delete-all-player-success"; + public static final String DELETE_ALL_PLAYER_WARNING = "delete-all-player-warning"; + public static final String DELETE_CLAIM_SUCCESS = "delete-claim-success"; + public static final String DELETE_CLAIM_WARNING = "delete-claim-warning"; + public static final String ECONOMY_BLOCK_AVAILABLE_PURCHASE_2D = "economy-block-available-purchase-2d"; + public static final String ECONOMY_BLOCK_AVAILABLE_PURCHASE_3D = "economy-block-available-purchase-3d"; + public static final String ECONOMY_BLOCK_PURCHASE_CONFIRMATION = "economy-block-purchase-confirmation"; + public static final String ECONOMY_BLOCK_PURCHASE_COST = "economy-block-purchase-cost"; + public static final String ECONOMY_BLOCK_PURCHASE_LIMIT = "economy-block-purchase-limit"; + public static final String ECONOMY_BLOCK_SALE_CONFIRMATION = "economy-block-sale-confirmation"; + public static final String ECONOMY_BLOCK_SELL_ERROR = "economy-block-sell-error"; + public static final String ECONOMY_CLAIM_ABANDON_SUCCESS = "economy-claim-abandon-success"; + public static final String ECONOMY_CLAIM_BUY_CANCELLED = "economy-claim-buy-cancelled"; + public static final String ECONOMY_CLAIM_BUY_CONFIRMATION = "economy-claim-buy-confirmation"; + public static final String ECONOMY_CLAIM_BUY_CONFIRMED = "economy-claim-buy-confirmed"; + public static final String ECONOMY_CLAIM_BUY_NOT_ENOUGH_FUNDS = "economy-claim-buy-not-enough-funds"; + public static final String ECONOMY_CLAIM_BUY_TRANSFER_CANCELLED = "economy-claim-buy-transfer-cancelled"; + public static final String ECONOMY_CLAIM_SALE_CONFIRMATION = "economy-claim-sale-confirmation"; + public static final String ECONOMY_CLAIM_SALE_CONFIRMED = "economy-claim-sale-confirmed"; + public static final String ECONOMY_CLAIM_SALE_INVALID_PRICE = "economy-claim-sale-invalid-price"; + public static final String ECONOMY_CLAIM_SOLD = "economy-claim-sold"; + public static final String ECONOMY_MODE_BLOCK_SALE_CONFIRMATION = "economy-mode-block-sale-confirmation"; + public static final String ECONOMY_MODE_RESIZE_SUCCESS_2D = "economy-mode-resize-success-2d"; + public static final String ECONOMY_MODE_RESIZE_SUCCESS_3D = "economy-mode-resize-success-3d"; + public static final String ECONOMY_NOT_ENOUGH_FUNDS = "economy-not-enough-funds"; + public static final String ECONOMY_PLAYER_NOT_FOUND = "economy-player-not-found"; + public static final String ECONOMY_WITHDRAW_ERROR = "economy-withdraw-error"; + public static final String FLAG_INVALID_CONTEXT = "flag-invalid-context"; + public static final String FLAG_INVALID_META = "flag-invalid-meta"; + public static final String FLAG_INVALID_TARGET = "flag-invalid-target"; + public static final String FLAG_NOT_FOUND = "flag-not-found"; + public static final String FLAG_NOT_SET = "flag-not-set"; + public static final String FLAG_OVERRIDDEN = "flag-overridden"; + public static final String FLAG_OVERRIDE_NOT_SUPPORTED = "flag-override-not-supported"; + public static final String FLAG_SET_PERMISSION_TARGET = "flag-set-permission-target"; + public static final String FLAG_UI_CLICK_TOGGLE = "flag-ui-click-toggle"; + public static final String FLAG_UI_INHERIT_PARENT = "flag-ui-inherit-parent"; + public static final String FLAG_UI_OVERRIDE_PERMISSION = "flag-ui-override-permission"; + public static final String OPTION_INVALID_CONTEXT = "option-invalid-context"; + public static final String OPTION_INVALID_TARGET = "option-invalid-target"; + public static final String OPTION_INVALID_VALUE = "option-invalid-value"; + public static final String OPTION_NOT_FOUND = "option-not-found"; + public static final String OPTION_NOT_SET = "option-not-set"; + public static final String OPTION_OVERRIDE_NOT_SUPPORTED = "option-override-not-supported"; + public static final String OPTION_RESET_SUCCESS = "option-reset-success"; + public static final String OPTION_SET_TARGET = "option-set-target"; + public static final String OPTION_UI_CLICK_TOGGLE = "option-ui-click-toggle"; + public static final String OPTION_UI_INHERIT_PARENT = "option-ui-inherit-parent"; + public static final String OPTION_UI_OVERRIDDEN = "option-ui-overridden"; + public static final String PERMISSION_ACCESS = "permission-access"; + public static final String PERMISSION_BAN_BLOCK = "permission-ban-block"; + public static final String PERMISSION_BAN_ENTITY = "permission-ban-entity"; + public static final String PERMISSION_BAN_ITEM = "permission-ban-item"; + public static final String PERMISSION_BUILD = "permission-build"; + public static final String PERMISSION_BUILD_NEAR_CLAIM = "permission-build-near-claim"; + public static final String PERMISSION_CLAIM_DELETE = "permission-claim-delete"; + public static final String PERMISSION_CLAIM_IGNORE = "permission-claim-ignore"; + public static final String PERMISSION_CLAIM_MANAGE = "permission-claim-manage"; + public static final String PERMISSION_CLAIM_RESET_FLAGS = "permission-claim-reset-flags"; + public static final String PERMISSION_INTERACT_BLOCK = "permission-interact-block"; + public static final String PERMISSION_INTERACT_ENTITY = "permission-interact-entity"; + public static final String PERMISSION_INTERACT_ITEM = "permission-interact-item"; + public static final String PERMISSION_INTERACT_ITEM_BLOCK = "permission-interact-item-block"; + public static final String PERMISSION_INTERACT_ITEM_ENTITY = "permission-interact-item-entity"; + public static final String PERMISSION_INVENTORY_OPEN = "permission-inventory-open"; + public static final String PERMISSION_ITEM_DROP = "permission-item-drop"; + public static final String PERMISSION_ITEM_USE = "permission-item-use"; + public static final String PERMISSION_PORTAL_ENTER = "permission-portal-enter"; + public static final String PERMISSION_PORTAL_EXIT = "permission-portal-exit"; + public static final String PERMISSION_PROTECTED_PORTAL = "permission-protected-portal"; + public static final String PERMISSION_TRUST = "permission-trust"; + public static final String PLAYER_ACCRUED_BLOCKS_EXCEEDED = "player-accrued-blocks-exceeded"; + public static final String PLAYER_REMAINING_BLOCKS_2D = "player-remaining-blocks-2d"; + public static final String PLAYER_REMAINING_BLOCKS_3D = "player-remaining-blocks-3d"; + public static final String PLAYERINFO_UI_ABANDON_RETURN_RATIO = "playerinfo-ui-abandon-return-ratio"; + public static final String PLAYERINFO_UI_BLOCK_ACCRUED = "playerinfo-ui-block-accrued"; + public static final String PLAYERINFO_UI_BLOCK_BONUS = "playerinfo-ui-block-bonus"; + public static final String PLAYERINFO_UI_CLAIM_LEVEL = "playerinfo-ui-claim-level"; + public static final String PLAYERINFO_UI_CLAIM_SIZE_LIMIT = "playerinfo-ui-claim-size-limit"; + public static final String PLAYERINFO_UI_BLOCK_INITIAL = "playerinfo-ui-block-initial"; + public static final String PLAYERINFO_UI_BLOCK_MAX_ACCRUED = "playerinfo-ui-block-max-accrued"; + public static final String PLAYERINFO_UI_BLOCK_REMAINING = "playerinfo-ui-block-remaining"; + public static final String PLAYERINFO_UI_BLOCK_TOTAL = "playerinfo-ui-block-total"; + public static final String PLAYERINFO_UI_CHUNK_TOTAL = "playerinfo-ui-chunk-total"; + public static final String PLAYERINFO_UI_CLAIM_TOTAL = "playerinfo-ui-claim-total"; + public static final String PLAYERINFO_UI_ECONOMY_BLOCK_AVAILABLE_PURCHASE = "playerinfo-ui-economy-block-available-purchase"; + public static final String PLAYERINFO_UI_ECONOMY_BLOCK_COST = "playerinfo-ui-economy-block-cost"; + public static final String PLAYERINFO_UI_ECONOMY_BLOCK_SELL_RETURN = "playerinfo-ui-economy-block-sell-return"; + public static final String PLAYERINFO_UI_LAST_ACTIVE = "playerinfo-ui-last-active"; + public static final String PLAYERINFO_UI_TAX_CURRENT_RATE = "playerinfo-ui-tax-current-rate"; + public static final String PLAYERINFO_UI_TAX_GLOBAL_CLAIM_RATE = "playerinfo-ui-tax-global-claim-rate"; + public static final String PLAYERINFO_UI_TAX_GLOBAL_TOWN_RATE = "playerinfo-ui-tax-global-town-rate"; + public static final String PLAYERINFO_UI_TAX_TOTAL = "playerinfo-ui-tax-total"; + public static final String PLAYERINFO_UI_UUID = "playerinfo-ui-uuid"; + public static final String PLAYERINFO_UI_WORLD = "playerinfo-ui-world"; + public static final String PLUGIN_COMMAND_NOT_FOUND = "plugin-command-not-found"; + public static final String PLUGIN_NOT_FOUND = "plugin-not-found"; + public static final String REGISTRY_BLOCK_NOT_FOUND = "registry-type-not-found"; + public static final String REGISTRY_ENTITY_NOT_FOUND = "registry-entity-not-found"; + public static final String REGISTRY_ITEM_NOT_FOUND = "registry-item-not-found"; + public static final String RESIZE_SUCCESS_2D = "resize-success-2d"; + public static final String RESIZE_SUCCESS_3D = "resize-success-3d"; + public static final String RESULT_TYPE_CHANGE_DENY = "result-type-change-deny"; + public static final String RESULT_TYPE_CHANGE_NOT_ADMIN = "result-type-change-not-admin"; + public static final String RESULT_TYPE_CHILD_SAME = "result-type-child-same"; + public static final String RESULT_TYPE_CREATE_DENY = "result-type-create-deny"; + public static final String RESULT_TYPE_NO_CHILDREN = "result-type-no-children"; + public static final String RESULT_TYPE_ONLY_SUBDIVISION = "result-type-only-subdivision"; + public static final String RESULT_TYPE_REQUIRES_OWNER = "result-type-requires-owner"; + public static final String SCHEMATIC_DELETED = "schematic-deleted"; + public static final String SCHEMATIC_NONE = "schematic-none"; + public static final String SCHEMATIC_RESTORE_CLICK = "schematic-restore-click"; + public static final String SCHEMATIC_RESTORE_CONFIRMATION = "schematic-restore-confirmation"; + public static final String SCHEMATIC_RESTORE_CONFIRMED = "schematic-restore-confirmed"; + public static final String SPAWN_SET_SUCCESS = "spawn-set-success"; + public static final String SPAWN_TELEPORT = "spawn-teleport"; + public static final String TAX_CLAIM_EXPIRED = "tax-claim-expired"; + public static final String TAX_CLAIM_PAID_BALANCE = "tax-claim-paid-balance"; + public static final String TAX_CLAIM_PAID_PARTIAL = "tax-claim-paid-partial"; + public static final String TAX_INFO = "tax-info"; + public static final String TAX_PAST_DUE = "tax-past-due"; + public static final String TELEPORT_CONFIRM = "teleport-confirm"; + public static final String TELEPORT_DELAY_NOTICE = "teleport-delay-notice"; + public static final String TOOL_NOT_EQUIPPED = "tool-not-equipped"; + public static final String TOWN_CREATE_NOT_ENOUGH_FUNDS = "town-create-not-enough-funds"; + public static final String TOWN_NAME = "town-name"; + public static final String TOWN_TAG = "town-tag"; + public static final String TRUST_ALREADY_HAS = "trust-already-has"; + public static final String TRUST_GRANT = "trust-grant"; + public static final String TRUST_INDIVIDUAL_ALL_CLAIMS = "trust-individual-all-claims"; + public static final String TRUST_PLUGIN_CANCEL = "trust-plugin-cancel"; + public static final String TUTORIAL_CLAIM_BASIC = "tutorial-claim-basic"; + public static final String UI_CLICK_FILTER_TYPE = "ui-click-filter-type"; + public static final String UNTRUST_INDIVIDUAL_ALL_CLAIMS = "untrust-individual-all-claims"; + public static final String UNTRUST_INDIVIDUAL_SINGLE_CLAIM = "untrust-individual-single-claim"; + public static final String UNTRUST_OWNER = "untrust-owner"; + + + @SuppressWarnings({"unchecked", "rawtypes"}) + public MessageStorage(Path path) { + + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(MessageDataConfig.class).bindToNew(); + + if (reload()) { + save(); + } + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize configuration", e); + } + } + + public MessageDataConfig getConfig() { + return this.configBase; + } + + public void save() { + try { + this.configMapper.serialize(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + this.loader.save(this.root); + } catch (IOException | ObjectMappingException e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to save configuration", e); + } + } + + public boolean reload() { + try { + this.root = this.loader.load(ConfigurationOptions.defaults()); + this.configBase = this.configMapper.populate(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + MESSAGE_DATA = this.configBase; + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration", e); + return false; + } + return true; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public void resetMessageData(String message) { + for (Map.Entry<Object, ? extends CommentedConfigurationNode> mapEntry : this.root.getNode(GriefDefenderPlugin.MOD_ID).getChildrenMap().entrySet()) { + CommentedConfigurationNode node = (CommentedConfigurationNode) mapEntry.getValue(); + String key = ""; + String comment = node.getComment().orElse(null); + if (comment == null && node.getKey() instanceof String) { + key = (String) node.getKey(); + if (key.equalsIgnoreCase(message)) { + this.root.getNode(GriefDefenderPlugin.MOD_ID).removeChild(mapEntry.getKey()); + } + } + } + + try { + this.loader.save(this.root); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(MessageDataConfig.class).bindToNew(); + this.configBase = this.configMapper.populate(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + } catch (IOException | ObjectMappingException e) { + e.printStackTrace(); + } + + GriefDefenderPlugin.getInstance().messageData = this.configBase; + } + + public CommentedConfigurationNode getRootNode() { + return this.root.getNode(GriefDefenderPlugin.MOD_ID); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/PlayerDataConfig.java b/sponge/src/main/java/com/griefdefender/configuration/PlayerDataConfig.java new file mode 100644 index 0000000..1966426 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/PlayerDataConfig.java @@ -0,0 +1,78 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.configuration.category.ConfigCategory; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class PlayerDataConfig extends ConfigCategory { + + private boolean requiresSave = true; + + @Setting(value = "accrued-claim-blocks", comment = "How many claim blocks the player has earned in world via play time.") + private int accruedClaimBlocks; + @Setting(value = "bonus-claim-blocks", + comment = "How many claim blocks the player has been gifted in world by admins, or purchased via economy integration.") + private int bonusClaimBlocks = 0; + @Setting(value = "migrated-blocks") + private boolean migrated = false; + + public int getAccruedClaimBlocks() { + return this.accruedClaimBlocks; + } + + public int getBonusClaimBlocks() { + return this.bonusClaimBlocks; + } + + public void setAccruedClaimBlocks(int blocks) { + this.requiresSave = true; + this.accruedClaimBlocks = blocks; + } + + public void setBonusClaimBlocks(int blocks) { + this.requiresSave = true; + this.bonusClaimBlocks = blocks; + } + + public boolean requiresSave() { + return this.requiresSave; + } + + public void setRequiresSave(boolean flag) { + this.requiresSave = flag; + } + + // Remove after 4.0 + public boolean hasMigratedBlocks() { + return this.migrated; + } + + public void setMigratedBlocks(boolean flag) { + this.migrated = flag; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/PlayerStorageData.java b/sponge/src/main/java/com/griefdefender/configuration/PlayerStorageData.java new file mode 100644 index 0000000..4ce1cd1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/PlayerStorageData.java @@ -0,0 +1,109 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.GriefDefenderPlugin; +import ninja.leaping.configurate.ConfigurationOptions; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.commented.SimpleCommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMapper; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PlayerStorageData { + + private HoconConfigurationLoader loader; + private CommentedConfigurationNode root = SimpleCommentedConfigurationNode.root(ConfigurationOptions.defaults()); + private ObjectMapper<PlayerDataConfig>.BoundInstance configMapper; + private PlayerDataConfig configBase; + + @SuppressWarnings({"unchecked", "rawtypes"}) + public PlayerStorageData(Path path) { + + try { + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + if (Files.notExists(path)) { + Files.createFile(path); + } + + this.loader = HoconConfigurationLoader.builder().setPath(path).build(); + this.configMapper = (ObjectMapper.BoundInstance) ObjectMapper.forClass(PlayerDataConfig.class).bindToNew(); + this.root = this.loader.load(ConfigurationOptions.defaults()); + CommentedConfigurationNode rootNode = this.root.getNode(GriefDefenderPlugin.MOD_ID); + // Check if server is using existing Sponge GP data + if (rootNode.isVirtual()) { + // check GriefPrevention + CommentedConfigurationNode gpRootNode = this.root.getNode("GriefPrevention"); + if (!gpRootNode.isVirtual()) { + rootNode.setValue(gpRootNode.getValue()); + gpRootNode.setValue(null); + } + } + this.configBase = this.configMapper.populate(rootNode); + save(); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to initialize configuration", e); + } + } + + public PlayerDataConfig getConfig() { + return this.configBase; + } + + public void save() { + try { + if (this.configBase != null) { + if (this.configBase.requiresSave()) { + this.configMapper.serialize(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + this.loader.save(this.root); + this.configBase.setRequiresSave(false); + } + } + } catch (IOException | ObjectMappingException e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to save configuration", e); + } + } + + public boolean reload() { + try { + this.root = this.loader.load(ConfigurationOptions.defaults()); + this.configBase = this.configMapper.populate(this.root.getNode(GriefDefenderPlugin.MOD_ID)); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Failed to load configuration", e); + return false; + } + return true; + } + + public CommentedConfigurationNode getRootNode() { + return this.root.getNode(GriefDefenderPlugin.MOD_ID); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/TownDataConfig.java b/sponge/src/main/java/com/griefdefender/configuration/TownDataConfig.java new file mode 100644 index 0000000..8ba7399 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/TownDataConfig.java @@ -0,0 +1,81 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.api.data.TownData; +import net.kyori.text.Component; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +@ConfigSerializable +public class TownDataConfig extends ClaimDataConfig implements TownData { + + @Setting + private Component townTag; + @Setting + private Map<UUID, Integer> accruedBlocks = new HashMap<>(); + @Setting + private Map<UUID, Integer> bonusBlocks = new HashMap<>(); + @Setting + private Map<UUID, Integer> createMode = new HashMap<>(); + @Setting + private Map<UUID, String> residentPastDueTaxTimestamps = new HashMap<>(); + @Setting + private Map<UUID, Double> residentTaxBalances = new HashMap<>(); + + @Override + public Optional<Component> getTownTag() { + return Optional.ofNullable(this.townTag); + } + + public void setTownTag(Component tag) { + this.townTag = tag; + } + + public Map<UUID, Integer> getAccruedClaimBlocks() { + return this.accruedBlocks; + } + + public Map<UUID, Integer> getBonusClaimBlocks() { + return this.bonusBlocks; + } + + public Map<UUID, Integer> getCreateModes() { + return this.createMode; + } + + public Map<UUID, String> getResidentPastDueTaxTimestamps() { + return this.residentPastDueTaxTimestamps; + } + + public Map<UUID, Double> getResidentTaxBalances() { + return this.residentTaxBalances; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/TownStorageData.java b/sponge/src/main/java/com/griefdefender/configuration/TownStorageData.java new file mode 100644 index 0000000..df8acc9 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/TownStorageData.java @@ -0,0 +1,45 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration; + +import com.griefdefender.api.claim.ClaimTypes; + +import java.nio.file.Path; +import java.util.UUID; + +public class TownStorageData extends ClaimStorageData { + + public TownStorageData(Path path, UUID worldUniqueId, UUID ownerUniqueId, boolean cuboid) { + super(path, worldUniqueId, ownerUniqueId, ClaimTypes.TOWN, cuboid); + } + + public TownStorageData(Path path, UUID worldUniqueId) { + super(path, worldUniqueId); + } + + public TownDataConfig getConfig() { + return (TownDataConfig) this.configBase; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/BanCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/BanCategory.java new file mode 100644 index 0000000..624e784 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/BanCategory.java @@ -0,0 +1,184 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +@ConfigSerializable +public class BanCategory extends ConfigCategory { + + @Setting(value = "blocks") + private Map<String, Component> blocks = new HashMap<>(); + @Setting(value = "entities") + private Map<String, Component> entities = new HashMap<>(); + @Setting(value = "items") + private Map<String, Component> items = new HashMap<>(); + + + public Map<String, Component> getBlockMap() { + return this.blocks; + } + + public Map<String, Component> getEntityMap() { + return this.entities; + } + + public Map<String, Component> getItemMap() { + return this.items; + } + + public boolean isBlockBanned(String id) { + if (id == null) { + return false; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.blocks.containsKey(id); + } + + public void addBlockBan(String id, Component reason) { + if (id == null) { + return; + } + if (reason == null) { + reason = TextComponent.empty(); + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + this.blocks.put(id, reason); + } + + public void removeBlockBan(String id) { + if (id == null) { + return; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + this.blocks.remove(id); + } + + public Component getBlockBanReason(String id) { + if (id == null) { + return null; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.blocks.get(id); + } + + public boolean isEntityBanned(String id) { + if (id == null) { + return false; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.entities.containsKey(id); + } + + public void addEntityBan(String id, Component reason) { + if (id == null) { + return; + } + if (reason == null) { + reason = TextComponent.empty(); + } + this.entities.put(id, reason); + } + + public void removeEntityBan(String id) { + if (id == null) { + return; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + this.entities.remove(id); + } + + public Component getEntityBanReason(String id) { + if (id == null) { + return null; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.entities.get(id); + } + + public boolean isItemBanned(String id) { + if (id == null) { + return false; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.items.containsKey(id); + } + + public void addItemBan(String id, Component reason) { + if (id == null) { + return; + } + if (reason == null) { + reason = TextComponent.empty(); + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + this.items.put(id, reason); + } + + public void removeItemBan(String id) { + if (id == null) { + return; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + this.items.remove(id); + } + + public Component getItemBanReason(String id) { + if (id == null) { + return null; + } + if (!id.contains(":")) { + id = "minecraft:" + id; + } + return this.items.get(id); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/BlacklistCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/BlacklistCategory.java new file mode 100644 index 0000000..ad94ddd --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/BlacklistCategory.java @@ -0,0 +1,71 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.registry.FlagRegistryModule; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.checkerframework.checker.nullness.qual.Nullable; + +@ConfigSerializable +public class BlacklistCategory extends ConfigCategory { + + @Setting(value = "flag-id-blacklist", comment = "A list of id's ignored by flags.") + public Map<String, List<String>> flagIdBlacklist = new HashMap<>(); + + @Setting(value = "global-source", comment = "A global list of source id's that are ignored by events. \nNote: This only affects events where the id specified is the source.") + public List<String> globalSourceBlacklist = new ArrayList<>(); + + @Setting(value = "global-target", comment = "A global list of target id's that are ignored by events. \nNote: This only affects events where the id specified is the target.") + public List<String> globalTargetBlacklist = new ArrayList<>(); + + public List<String> getGlobalSourceBlacklist() { + return this.globalSourceBlacklist; + } + + public List<String> getGlobalTargetBlacklist() { + return this.globalTargetBlacklist; + } + + public BlacklistCategory() { + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + this.flagIdBlacklist.put(flag.getId().toLowerCase(), new ArrayList<>()); + } + this.flagIdBlacklist.put("block-pre", new ArrayList<>()); + } + + // Used by API + @Nullable + public List<String> getFlagBlacklist(String flag) { + return this.flagIdBlacklist.get(flag); + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/ClaimCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/ClaimCategory.java new file mode 100644 index 0000000..7fa903b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/ClaimCategory.java @@ -0,0 +1,68 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class ClaimCategory extends ConfigCategory { + + @Setting(value = "auto-chest-claim-block-radius", comment = "Radius used (in blocks) for auto-created claim when a chest is placed. Set to -1 to disable chest claim creation.") + public int autoChestClaimBlockRadius = 4; + @Setting(value = "border-block-radius", comment = "Set claim border of specified radius (in blocks), centered on claim. If set to 1, adds an additional 1 block protected radius around claim.\n" + + "Note: It is not recommended to set this value too high as performance can degrade due to deeper claim searches.") + public int borderBlockRadius = 0; + @Setting(value = "claim-list-max", comment = "Controls the max displayed claims when using the '/claimlist' command. Default: 200") + public int claimListMax = 200; + @Setting(value = "expiration-cleanup-interval", comment = "The interval in minutes for cleaning up expired claims. Default: 0. Set to 0 to disable.") + public int expirationCleanupInterval = 0; + @Setting(value = "auto-nature-restore", comment = "Whether survival claims will be automatically restored to world generated state when expired. \nNote: This only supports world generated blocks. Consider using 'auto-schematic-restore' if using a custom world.") + public boolean claimAutoNatureRestore = false; + @Setting(value = "auto-schematic-restore", comment = "Whether survival claims will be automatically restored to its claim creation schematic on abandon/expiration. " + + "\nNote: Enabling this feature will cause ALL newly created claims to automatically create a special schematic that will be used to restore claim on abandon/expiration." + + "\nNote: Enabling this feature will disable ability to resize claims." + + "\nNote: It is HIGHLY recommended to disable building in the wilderness before using this feature to avoid players exploiting." + + "\nNote: It is also recommended to ONLY use this feature in newly created worlds where there is no existing player data." + + "\nNote: This does NOT affect deletions. If admins want to restore back to original schematic, they can select '__restore__' by using /claimschematic command.") + public boolean claimAutoSchematicRestore = false; + @Setting(value = "investigation-tool", comment = "The item used to investigate claims with a right-click.\nNote: Set to empty quotes if you want to assign no item and use '/claim' mode exclusively.") + public String investigationTool = "minecraft:stick"; + @Setting(value = "modification-tool", comment = "The item used to create/resize claims with a right click.\nNote: Set to empty quotes if you want to assign no item and use '/claim' mode exclusively.") + public String modificationTool = "minecraft:golden_shovel"; + @Setting(value = "claims-enabled", + comment = "Whether claiming is enabled or not. (0 = Disabled, 1 = Enabled)") + public int claimsEnabled = 1; + @Setting(value = "bank-tax-system", comment = "Whether to enable the bank/tax system for claims. Set to true to enable.") + public boolean bankTaxSystem = false; + @Setting(value = "tax-apply-hour", comment = "The specific hour in day to apply tax to all claims.") + public int taxApplyHour = 12; + @Setting(value = "bank-transaction-log-limit") + public int bankTransactionLogLimit = 60; + + public ClaimCategory() { + + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/ConfigCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/ConfigCategory.java new file mode 100644 index 0000000..4c9f89e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/ConfigCategory.java @@ -0,0 +1,32 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public abstract class ConfigCategory { + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupCategory.java new file mode 100644 index 0000000..11f4464 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupCategory.java @@ -0,0 +1,72 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import java.util.HashMap; +import java.util.Map; + +import com.griefdefender.permission.flag.GDCustomFlagDefinition; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class CustomFlagGroupCategory extends ConfigCategory { + + @Setting + boolean enabled = true; + @Setting(value = "admin-group", comment = "Set to true if this flag group is for admin use only." + + "\nNote: If admin group, the permission is 'griefdefender.admin.custom.flag.<groupname>" + + "\nNote: If user group (admin set false), the permission is 'griefdefender.user.custom.flag.<groupname>") + boolean isAdmin = false; + @Setting(value = "title", comment = "The title text to be used for TAB display.") + Component titleText = TextComponent.empty(); + @Setting(value = "hover", comment = "The hover text to be displayed when hovering over group name in GUI.") + Component hoverText = TextComponent.empty(); + @Setting + Map<String, GDCustomFlagDefinition> definitions = new HashMap<>(); + + public Map<String, GDCustomFlagDefinition> getFlagDefinitions() { + return this.definitions; + } + + public Component getTitleComponent() { + return this.titleText; + } + + public Component getHoverComponent() { + return this.hoverText; + } + + public boolean isEnabled() { + return this.enabled; + } + + public boolean isAdminGroup() { + return this.isAdmin; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupDefinitionCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupDefinitionCategory.java new file mode 100644 index 0000000..91670a6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/CustomFlagGroupDefinitionCategory.java @@ -0,0 +1,70 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import java.util.HashMap; +import java.util.Map; + +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.permission.flag.GDCustomFlagDefinition; +import com.griefdefender.permission.flag.GDCustomFlagDefinitions; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class CustomFlagGroupDefinitionCategory extends ConfigCategory { + + @Setting + Map<String, CustomFlagGroupCategory> groups = new HashMap<>(); + + public Map<String, CustomFlagGroupCategory> getGroups() { + return this.groups; + } + + public void initDefaults() { + CustomFlagGroupCategory userGroup = this.groups.get("user"); + CustomFlagGroupCategory adminGroup = this.groups.get("admin"); + if (userGroup == null) { + userGroup = new CustomFlagGroupCategory(); + } + if (userGroup.isEnabled() && userGroup.getFlagDefinitions().isEmpty()) { + for (GDCustomFlagDefinition definition : GDCustomFlagDefinitions.USER_FLAGS) { + userGroup.getFlagDefinitions().put(definition.getDisplayName(), definition); + } + this.groups.put("user", userGroup); + } + if (adminGroup == null) { + adminGroup = new CustomFlagGroupCategory(); + } + if (adminGroup.isEnabled() && adminGroup.getFlagDefinitions().isEmpty()) { + for (GDCustomFlagDefinition definition : GDCustomFlagDefinitions.ADMIN_FLAGS) { + adminGroup.getFlagDefinitions().put(definition.getDisplayName(), definition); + } + adminGroup.isAdmin = true; + this.groups.put("admin", adminGroup); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/DefaultPermissionCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/DefaultPermissionCategory.java new file mode 100644 index 0000000..cb37697 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/DefaultPermissionCategory.java @@ -0,0 +1,205 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import com.google.common.collect.Maps; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.registry.ClaimTypeRegistryModule; +import com.griefdefender.registry.FlagRegistryModule; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.HashMap; +import java.util.Map; + +@ConfigSerializable +public class DefaultPermissionCategory extends ConfigCategory { + + @Setting(value = "default-claim-flags", comment = "The default flag settings used by claims. The group name represents the claim type." + + "\nEx: The group admin will ONLY affect admin claims." + + "\nSupported groups are : global, admin, basic, subdivision, town, and wilderness." + + "\nNote: Global represents all claim types." + + "\nNote: Specific types, such as wilderness, have higher priority than global." + + "\nNote: Defaults do not force flags onto user claims. A newly created claim will have no flags set and use these default settings until a claim owner sets flags.") + private Map<String, Map<String, Boolean>> defaultClaimFlags = Maps.newHashMap(); + + @Setting(value = "default-user-options", comment = "The default user options for all players.\nNote: Setting default claim type options will override this.") + private Map<String, String> defaultUserOptions = new HashMap<>(); + + @Setting(value = "default-user-basic-options", comment = "The default options applied to users for basic claims.\nNote: These options override default global options.") + private Map<String, String> defaultBasicOptions = new HashMap<>(); + + @Setting(value = "default-user-subdivision-options", comment = "The default options applied to users for subdivisions.\nNote: These options override default global options.") + private Map<String, String> defaultSubdivisionOptions = new HashMap<>(); + + @Setting(value = "default-user-town-options", comment = "The default options applied to users for towns.\nNote: These options override default global options.") + private Map<String, String> defaultTownOptions = new HashMap<>(); + + public DefaultPermissionCategory() { + Map<String, Boolean> globalFlagMap = new HashMap<>(); + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + globalFlagMap.put(flag.getName(), flag.getDefaultClaimTypeValue(null)); + } + this.defaultClaimFlags.put("global", globalFlagMap); + Map<String, Boolean> wildernessFlagMap = new HashMap<>(); + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + wildernessFlagMap.put(flag.getName(), flag.getDefaultClaimTypeValue(ClaimTypes.WILDERNESS)); + } + this.defaultClaimFlags.put(ClaimTypes.WILDERNESS.getName().toLowerCase(), wildernessFlagMap); + + final int maxAccruedBlocks = GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME ? 20480000 : 80000; + this.defaultUserOptions.put(Options.EXPIRATION.getName(), "14"); + this.defaultUserOptions.put(Options.TAX_EXPIRATION.getName(), "7"); + this.defaultUserOptions.put(Options.TAX_EXPIRATION_DAYS_KEEP.getName(), "7"); + this.defaultUserOptions.put(Options.TAX_RATE.getName(), "1.0"); + this.defaultUserOptions.put(Options.BLOCKS_ACCRUED_PER_HOUR.getName(), "120"); + this.defaultUserOptions.put(Options.CHEST_EXPIRATION.getName(), "7"); + this.defaultUserOptions.put(Options.CREATE_LIMIT.getName(), "-1"); + this.defaultUserOptions.put(Options.CREATE_MODE.getName(), "undefined"); + this.defaultUserOptions.put(Options.ECONOMY_BLOCK_COST.getName(), "0.0"); + this.defaultUserOptions.put(Options.ECONOMY_BLOCK_SELL_RETURN.getName(), "0.0"); + this.defaultUserOptions.put(Options.INITIAL_BLOCKS.getName(), "120"); + this.defaultUserOptions.put(Options.MAX_ACCRUED_BLOCKS.getName(), Integer.toString(maxAccruedBlocks)); + this.defaultUserOptions.put(Options.MIN_LEVEL.getName(), "0"); + this.defaultUserOptions.put(Options.MAX_LEVEL.getName(), "255"); + this.defaultUserOptions.put(Options.ABANDON_DELAY.getName(), "0"); + this.defaultUserOptions.put(Options.ABANDON_RETURN_RATIO.getName(), "1.0"); + this.defaultUserOptions.put(Options.RAID.getName(), "true"); + this.defaultUserOptions.put(Options.SPAWN_LIMIT.getName(), "-1"); + this.defaultUserOptions.put(Options.PLAYER_DENY_FLIGHT.getName(), "false"); + this.defaultUserOptions.put(Options.PLAYER_DENY_GODMODE.getName(), "false"); + this.defaultUserOptions.put(Options.PLAYER_DENY_HUNGER.getName(), "false"); + this.defaultUserOptions.put(Options.PLAYER_GAMEMODE.getName(), "undefined"); + this.defaultUserOptions.put(Options.PLAYER_HEALTH_REGEN.getName(), "-1.0"); + this.defaultUserOptions.put(Options.PLAYER_KEEP_INVENTORY.getName(), "undefined"); + this.defaultUserOptions.put(Options.PLAYER_KEEP_LEVEL.getName(), "undefined"); + this.defaultUserOptions.put(Options.PLAYER_TELEPORT_DELAY.getName(), "0"); + this.defaultUserOptions.put(Options.PLAYER_WALK_SPEED.getName(), "-1"); + this.defaultUserOptions.put(Options.PLAYER_WEATHER.getName(), "undefined"); + this.defaultUserOptions.put(Options.PVP.getName(), "undefined"); + + this.defaultBasicOptions.put(Options.MIN_SIZE_X.getName(), "5"); + this.defaultBasicOptions.put(Options.MIN_SIZE_Y.getName(), "5"); + this.defaultBasicOptions.put(Options.MIN_SIZE_Z.getName(), "5"); + this.defaultBasicOptions.put(Options.MAX_SIZE_X.getName(), "0"); + this.defaultBasicOptions.put(Options.MAX_SIZE_Y.getName(), "256"); + this.defaultBasicOptions.put(Options.MAX_SIZE_Z.getName(), "0"); + + this.defaultSubdivisionOptions.put(Options.MIN_SIZE_X.getName(), "1"); + this.defaultSubdivisionOptions.put(Options.MIN_SIZE_Y.getName(), "1"); + this.defaultSubdivisionOptions.put(Options.MIN_SIZE_Z.getName(), "1"); + this.defaultSubdivisionOptions.put(Options.MAX_SIZE_X.getName(), "1000"); + this.defaultSubdivisionOptions.put(Options.MAX_SIZE_Y.getName(), "256"); + this.defaultSubdivisionOptions.put(Options.MAX_SIZE_Z.getName(), "1000"); + + this.defaultTownOptions.put(Options.CREATE_LIMIT.getName(), "1"); + this.defaultTownOptions.put(Options.MIN_LEVEL.getName(), "0"); + this.defaultTownOptions.put(Options.MIN_SIZE_X.getName(), "32"); + this.defaultTownOptions.put(Options.MIN_SIZE_Y.getName(), "32"); + this.defaultTownOptions.put(Options.MIN_SIZE_Z.getName(), "32"); + this.defaultTownOptions.put(Options.MAX_LEVEL.getName(), "255"); + this.defaultTownOptions.put(Options.MAX_SIZE_X.getName(), "0"); + this.defaultTownOptions.put(Options.MAX_SIZE_Y.getName(), "256"); + this.defaultTownOptions.put(Options.MAX_SIZE_Z.getName(), "0"); + this.defaultTownOptions.put(Options.TAX_EXPIRATION.getName(), "7"); + this.defaultTownOptions.put(Options.TAX_EXPIRATION_DAYS_KEEP.getName(), "7"); + this.defaultTownOptions.put(Options.TAX_RATE.getName(), "1.0"); + } + + public void checkOptions() { + Map<String, String> options = new HashMap<>(this.defaultUserOptions); + for (Map.Entry<String, String> mapEntry : options.entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(Options.CREATE_LIMIT.getName()) && mapEntry.getValue().equals("0")) { + this.defaultUserOptions.put(mapEntry.getKey(), "undefined"); + break; + } + } + options = new HashMap<>(this.defaultBasicOptions); + for (Map.Entry<String, String> mapEntry : options.entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(Options.CREATE_LIMIT.getName()) && mapEntry.getValue().equals("0")) { + this.defaultBasicOptions.put(mapEntry.getKey(), "undefined"); + break; + } + } + options = new HashMap<>(this.defaultSubdivisionOptions); + for (Map.Entry<String, String> mapEntry : options.entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(Options.CREATE_LIMIT.getName()) && mapEntry.getValue().equals("0")) { + this.defaultSubdivisionOptions.put(mapEntry.getKey(), "undefined"); + break; + } + } + options = new HashMap<>(this.defaultTownOptions); + for (Map.Entry<String, String> mapEntry : options.entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(Options.CREATE_LIMIT.getName()) && mapEntry.getValue().equals("0")) { + this.defaultTownOptions.put(mapEntry.getKey(), "undefined"); + break; + } + } + } + + public void refreshFlags() { + for (ClaimType type : ClaimTypeRegistryModule.getInstance().getAll()) { + final Map<String, Boolean> flagTypeMap = this.defaultClaimFlags.get(type.getName().toLowerCase()); + if (flagTypeMap != null) { + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + if (!flagTypeMap.containsKey(flag.getName())) { + flagTypeMap.put(flag.getName(), flag.getDefaultClaimTypeValue(type)); + } + } + } + } + final Map<String, Boolean> globalFlagMap = this.defaultClaimFlags.get("global"); + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + if (!globalFlagMap.containsKey(flag.getName())) { + globalFlagMap.put(flag.getName(), flag.getDefaultClaimTypeValue(null)); + } + } + } + + public Map<String, Boolean> getFlagDefaults(String type) { + return this.defaultClaimFlags.get(type.toLowerCase()); + } + + public Map<String, String> getBasicOptionDefaults() { + return this.defaultBasicOptions; + } + + public Map<String, String> getSubdivisionOptionDefaults() { + return this.defaultSubdivisionOptions; + } + + public Map<String, String> getTownOptionDefaults() { + return this.defaultTownOptions; + } + + public Map<String, String> getUserOptionDefaults() { + return this.defaultUserOptions; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/EconomyCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/EconomyCategory.java new file mode 100644 index 0000000..068a934 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/EconomyCategory.java @@ -0,0 +1,41 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class EconomyCategory extends ConfigCategory { + + @Setting(value = "economy-mode", comment = "Uses economy instead of player claim blocks for claim creation." + + "\nIf true, disables the claim block system in favor of economy." + + "\nNote: Using this mode disables the '/buyblocks' command as claim creation will pull funds directly from a player's economy balance." + + "\nNote: If players have existing claimblocks from past configurations, they can use the '/sellblocks' command to convert remainder to currency.") + public boolean economyMode = false; + @Setting(value = "use-claim-block-task", comment = "Claim blocks earned will be converted to economy based on 'claim-block-cost'." + + "\n(Default: false)\nNote: This setting can only be used if 'economy-mode' is true.") + public boolean useClaimBlockTask = false; +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/GeneralCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/GeneralCategory.java new file mode 100644 index 0000000..54f7936 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/GeneralCategory.java @@ -0,0 +1,35 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class GeneralCategory extends ConfigCategory { + + @Setting(value = "max-claim-inspection-distance", comment = "The max claim inspection block distance. (Default: 100)") + public int maxClaimInspectionDistance = 100; +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/MessageCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/MessageCategory.java new file mode 100644 index 0000000..ad89e9d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/MessageCategory.java @@ -0,0 +1,44 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class MessageCategory extends ConfigCategory { + + @Setting(value = "locale", comment = "Set the locale to use for GD messages. (Default: en_US)\n" + + "Available languages: en_US, fr_FR, ru_RU. The data is stored under assets in jar.\n" + + "Note: The language code must be lowercase and the country code must be uppercase.") + public String locale = "en_US"; + + @Setting(value = "enter-exit-show-gd-prefix", comment = "Whether GD prefix should be shown in enter/exit claim messages. (Default: true)") + public boolean enterExitShowGdPrefix = true; + + @Setting(value = "enter-exit-chat-type", comment = "The default chat type to use when sending enter/claim messages to a player.\n" + + "(0 = Chat, 1 = ActionBar, 2 = Title)/nNote: ActionBar is only available in MC 1.11+") + public int enterExitChatType = 0; +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/MigratorCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/MigratorCategory.java new file mode 100644 index 0000000..5ad3754 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/MigratorCategory.java @@ -0,0 +1,53 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class MigratorCategory extends ConfigCategory { + + @Setting(value = "griefprevention-bukkit", comment = "Set to true to enable the griefprevention bukkit migrator." + + "\nNote: Migrates GP bukkit classic claim data and GPFlags data, if available, to current format." + + "\nNote: It is recommended to backup data before using.") + public boolean gpBukkitMigrator = false; + + @Setting(value = "griefprevention-sponge", comment = "Set to true to enable the griefprevention sponge migrator." + + "\nNote: Migrates GP sponge claim data to current format." + + "\nNote: It is recommended to backup data before using.") + public boolean gpSpongeMigrator = false; + + @Setting(value = "red-protect", comment = + "Set to true to enable RedProtect data migrator." + + "\nNote: All RedProtect data will be converted into basic claim data.") + public boolean redProtectMigrator = false; + + @Setting(value = "worldguard", comment = + "Set to true to enable WorldGuard data migrator." + + "\nNote: Only cuboid regions are supported." + + "\nNote: It is recommended to backup data before using.") + public boolean worldGuardMigrator = false; +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/ModuleCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/ModuleCategory.java new file mode 100644 index 0000000..1fb28a4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/ModuleCategory.java @@ -0,0 +1,56 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import com.google.common.collect.Maps; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.registry.FlagRegistryModule; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.Map; + +@ConfigSerializable +public class ModuleCategory { + + @Setting(value = "protection", comment = "Controls which protection modules are enabled." + + "\nNote: If you want full protection, it is recommended to keep everything enabled.") + private Map<String, Boolean> protection = Maps.newHashMap(); + + public ModuleCategory() { + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + this.protection.put(flag.getName().toLowerCase(), true); + } + } + + public boolean isProtectionModuleEnabled(String flag) { + final Boolean result = this.protection.get(flag); + if (result == null) { + return false; + } + + return result; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/OptionCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/OptionCategory.java new file mode 100644 index 0000000..4569a6e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/OptionCategory.java @@ -0,0 +1,64 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import com.griefdefender.api.permission.option.Options; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +import java.util.ArrayList; +import java.util.List; + +@ConfigSerializable +public class OptionCategory extends ConfigCategory { + + @Setting(value = "user-town-options", comment = "A list of options standard users can manage in their towns with the /co commands.") + private List<String> userTownOptions = new ArrayList<>(); + + public OptionCategory() { + this.userTownOptions.add(Options.ABANDON_RETURN_RATIO.toString()); + this.userTownOptions.add(Options.BLOCKS_ACCRUED_PER_HOUR.toString()); + this.userTownOptions.add(Options.EXPIRATION.toString()); + this.userTownOptions.add(Options.CREATE_LIMIT.toString()); + this.userTownOptions.add(Options.EXPIRATION.toString()); + this.userTownOptions.add(Options.INITIAL_BLOCKS.toString()); + this.userTownOptions.add(Options.MAX_ACCRUED_BLOCKS.toString()); + this.userTownOptions.add(Options.MAX_LEVEL.toString()); + this.userTownOptions.add(Options.MAX_SIZE_X.toString()); + this.userTownOptions.add(Options.MAX_SIZE_Y.toString()); + this.userTownOptions.add(Options.MAX_SIZE_Z.toString()); + this.userTownOptions.add(Options.MIN_LEVEL.toString()); + this.userTownOptions.add(Options.MIN_SIZE_X.toString()); + this.userTownOptions.add(Options.MIN_SIZE_Y.toString()); + this.userTownOptions.add(Options.MIN_SIZE_Z.toString()); + this.userTownOptions.add(Options.TAX_EXPIRATION.toString()); + this.userTownOptions.add(Options.TAX_EXPIRATION_DAYS_KEEP.toString()); + this.userTownOptions.add(Options.TAX_RATE.toString()); + } + + public List<String> getUserTownOptions() { + return this.userTownOptions; + } +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/PlayerDataCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/PlayerDataCategory.java new file mode 100644 index 0000000..375929a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/PlayerDataCategory.java @@ -0,0 +1,58 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import com.griefdefender.api.claim.ClaimBlockSystem; +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class PlayerDataCategory extends ConfigCategory { + + @Setting(value = "use-global-storage", comment = "Whether player data should be stored globally. False will store all data per world.") + public boolean useGlobalPlayerDataStorage = true; + @Setting(value = "claim-block-system", comment = "Determines which claim block system to use for claims. (Default: AREA)\nIf set to VOLUME, claim blocks will use the chunk count system to balance 3d claiming." + + "\nIf set to AREA, the standard 2d block count system will be used.") + public ClaimBlockSystem claimBlockSystem = ClaimBlockSystem.AREA; + @Setting(value = "migrate-volume-rate", comment = "The rate to multiply each accrued claim blocks total by." + + "\nSet to a value greater than -1 to enable. (Default: 256)." + + "\nNote: This should only be used when migrating from area (2D system) to volume (3D system)." + + "\nEach chunk is worth 65,536 blocks in the new system compared to 256 in old." + + "\nThis requires 'claim-block-system' to be set to VOLUME.") + public int migrateVolumeRate = -1; + @Setting(value = "migrate-area-rate", comment = "The rate to divide each accrued claim blocks total by." + + "\nSet to a value greater than -1 to enable. (Default: 256)." + + "\nNote: This should only be used when migrating from volume (3D system) to area (2D system)." + + "\nIn this system, a chunk costs 256 blocks." + + "\nThis requires 'claim-block-system' to be set to AREA.") + public int migrateAreaRate = -1; + @Setting(value = "reset-migrations", comment = "If enabled, resets all playerdata migration flags to allow for another migration." + + "\nNote: Use this with caution as it can easily mess up claim block data. It is highly recommended to backup before using.") + public boolean resetMigrations = false; + @Setting(value = "reset-accrued-claim-blocks", comment = "If enabled, resets all playerdata accrued claim blocks to match total cost of claims owned." + + "\nExample: If a player has 5 basic claims with a total cost of 1000, this will set their accrued claim blocks to 1000." + + "\nNote: This will also reset all bonus claim blocks to 0. It is highly recommended to backup before using.") + public boolean resetAccruedClaimBlocks = false; +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/PvpCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/PvpCategory.java new file mode 100644 index 0000000..1a2a7e1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/PvpCategory.java @@ -0,0 +1,35 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class PvpCategory extends ConfigCategory { + + @Setting(value = "combat-timeout", comment = "How long combat is considered to continue after the most recent damage.") + public int combatTimeout = 15; +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/ThreadCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/ThreadCategory.java new file mode 100644 index 0000000..b5c0a50 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/ThreadCategory.java @@ -0,0 +1,35 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class ThreadCategory extends ConfigCategory { + + @Setting(value = "executor-threads", comment = "The number of threads to use for GD's executor. (Default: 1)") + public int numExecutorThreads = 1; +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/TownCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/TownCategory.java new file mode 100644 index 0000000..2ad7ae1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/TownCategory.java @@ -0,0 +1,45 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; + +@ConfigSerializable +public class TownCategory extends ConfigCategory { + + @Setting(value = "cleanup-task-interval", comment = "The interval in minutes for restoring blocks in an expired town. Set to 0 to disable. Note: This only supports vanilla blocks. Use with caution if using custom biomes.") + public int cleanupTaskInterval = 5; + @Setting(value = "auto-nature-restore", comment = "Whether survival towns will be automatically restored to nature when auto-deleted.") + public boolean autoNatureRestore = false; + @Setting(value = "creation-cost", comment = "The required amount of funds to create a town. (Default: 0)\nNote: This requires an Economy plugin.") + public double cost = 0; + @Setting(value = "clan-require-town", comment = "If true, requires a town to be owned for MCClans.") + public boolean clanRequireTown = true; + + public TownCategory() { + + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/category/VisualCategory.java b/sponge/src/main/java/com/griefdefender/configuration/category/VisualCategory.java new file mode 100644 index 0000000..db499f7 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/category/VisualCategory.java @@ -0,0 +1,94 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.category; + +import ninja.leaping.configurate.objectmapping.Setting; +import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable; +import org.spongepowered.api.MinecraftVersion; +import org.spongepowered.api.Sponge; + +@ConfigSerializable +public class VisualCategory extends ConfigCategory { + + @Setting(value = "hide-borders-when-using-wecui", comment = "Whether to hide the glowstone/gold block borders when using WECUI.") + public boolean hideBorders = false; + @Setting(value = "hide-wecui-drag-visuals-2d", comment = "Whether drag visuals should be shown while creating a claim in 2D mode.") + public boolean hideDrag2d = true; + + @Setting(value = "claim-create-block", comment = "The visual block used during claim creation. (Default: minecraft:diamond_block)") + public String claimCreateStartBlock = "minecraft:diamond_block"; + + @Setting(value = "admin-accent-block", comment = "The visual accent block used for admin claims. (Default: minecraft:pumpkin)") + public String visualAdminAccentBlock = "minecraft:pumpkin"; + @Setting(value = "admin-corner-block", comment = "The visual corner block used for admin claims. (Default: minecraft:glowstone)") + public String visualAdminCornerBlock = "minecraft:glowstone"; + @Setting(value = "admin-filler-block", comment = "The visual filler block used for admin claims. (Default: minecraft:pumpkin)") + public String visualAdminFillerBlock = "minecraft:pumpkin"; + + @Setting(value = "basic-accent-block", comment = "The visual accent block used for basic claims. (Default: minecraft:gold_block)") + public String visualBasicAccentBlock = "minecraft:gold_block"; + @Setting(value = "basic-corner-block", comment = "The visual corner block used for basic claims. (Default: minecraft:glowstone)") + public String visualBasicCornerBlock = "minecraft:glowstone"; + @Setting(value = "basic-filler-block", comment = "The visual filler block used for basic claims. (Default: minecraft:gold_block)") + public String visualBasicFillerBlock = "minecraft:gold_block"; + + @Setting(value = "error-accent-block", comment = "The visual accent block used to visualize an error in a claim. (Default: minecraft:netherrack)") + public String visualErrorAccentBlock = "minecraft:netherrack"; + @Setting(value = "error-corner-block", comment = "The visual corner block used to visualize an error in a claim. (Default: minecraft:redstone_ore)") + public String visualErrorCornerBlock = "minecraft:redstone_ore"; + @Setting(value = "error-filler-block", comment = "The visual filler block used to visualize an error in a claim. (Default: minecraft:diamond_block)") + public String visualErrorFillerBlock = "minecraft:diamond_block"; + + @Setting(value = "subdivision-accent-block", comment = "The visual accent block used for subdivision claims. (Default: minecraft:white_wool or minecraft:wool for legacy versions)") + public String visualSubdivisionAccentBlock; + @Setting(value = "subdivision-corner-block", comment = "The visual corner block used for subdivision claims. (Default: minecraft:iron_block)") + public String visualSubdivisionCornerBlock = "minecraft:iron_block"; + @Setting(value = "subdivision-filler-block", comment = "The visual filler block used for subdivision claims. (Default: minecraft:white_wool or minecraft:wool for legacy versions)") + public String visualSubdivisionFillerBlock; + + @Setting(value = "town-accent-block", comment = "The visual accent block used for town claims. (Default: minecraft:emerald_block)") + public String visualTownAccentBlock = "minecraft:emerald_block"; + @Setting(value = "town-corner-block", comment = "The visual corner block used for town claims. (Default: minecraft:glowstone)") + public String visualTownCornerBlock = "minecraft:glowstone"; + @Setting(value = "town-filler-block", comment = "The visual filler block used for town claims. (Default: minecraft:emerald_block)") + public String visualTownFillerBlock = "minecraft:emerald_block"; + + @Setting(value = "nature-accent-block", comment = "The visual accent block used while in restore nature mode. (Default: minecraft:diamond_block)") + public String visualNatureAccentBlock = "minecraft:diamond_block"; + @Setting(value = "nature-corner-block", comment = "The visual corner block used while in restore nature mode. (Default: minecraft:diamond_block)") + public String visualNatureCornerBlock = "minecraft:diamond_block"; + + public VisualCategory() { + final MinecraftVersion version = Sponge.getPlatform().getMinecraftVersion(); + if (version.getName().contains("1.8.8") || version.getName().contains("1.12")) { + this.visualSubdivisionAccentBlock = "minecraft:wool"; + this.visualSubdivisionFillerBlock = "minecraft:wool"; + } else { + this.visualSubdivisionAccentBlock = "minecraft:white_wool"; + this.visualSubdivisionFillerBlock = "minecraft:white_wool"; + } + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/ClaimTypeSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/ClaimTypeSerializer.java new file mode 100644 index 0000000..fc48e3e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/ClaimTypeSerializer.java @@ -0,0 +1,50 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.registry.ClaimTypeRegistryModule; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +public class ClaimTypeSerializer implements TypeSerializer<ClaimType> { + + @Override + public ClaimType deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + ClaimType ret = ClaimTypeRegistryModule.getInstance().getById(node.getString().toLowerCase()).orElse(null); + if (ret == null) { + throw new ObjectMappingException("Input '" + node.getValue() + "' was not a valid value for type " + type); + } + return ret; + } + + @Override + public void serialize(TypeToken<?> type, ClaimType obj, ConfigurationNode node) throws ObjectMappingException { + node.setValue(obj.getName()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/ComponentConfigSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/ComponentConfigSerializer.java new file mode 100644 index 0000000..159af38 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/ComponentConfigSerializer.java @@ -0,0 +1,86 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import com.google.common.reflect.TypeToken; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.gson.GsonConfigurationLoader; +import ninja.leaping.configurate.loader.HeaderMode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; + +public class ComponentConfigSerializer implements TypeSerializer<Component> { + + /** + * Creates a new {@link ComponentConfigSerializer}. Normally this should not + * need to be created more than once. + */ + public ComponentConfigSerializer() { + } + + @Override + public Component deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + if (node.getString() == null || node.getString().isEmpty()) { + return TextComponent.empty(); + } + if (node.getString().contains("text=")) { + // Try sponge data + StringWriter writer = new StringWriter(); + + GsonConfigurationLoader gsonLoader = GsonConfigurationLoader.builder() + .setIndent(0) + .setSink(() -> new BufferedWriter(writer)) + .setHeaderMode(HeaderMode.NONE) + .build(); + + try { + gsonLoader.save(node); + } catch (IOException e) { + throw new ObjectMappingException(e); + } + return GsonComponentSerializer.INSTANCE.deserialize(writer.toString()); + } + + return LegacyComponentSerializer.legacy().deserialize(node.getString(), '&'); + } + + @Override + public void serialize(TypeToken<?> type, Component obj, ConfigurationNode node) throws ObjectMappingException { + if (obj == TextComponent.empty()) { + node.setValue(""); + } else { + node.setValue(LegacyComponentSerializer.legacy().serialize(obj, '&')); + } + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/CreateModeTypeSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/CreateModeTypeSerializer.java new file mode 100644 index 0000000..aadb03b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/CreateModeTypeSerializer.java @@ -0,0 +1,54 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; + +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +public class CreateModeTypeSerializer implements TypeSerializer<CreateModeType> { + + @Override + public CreateModeType deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + switch (node.getString().toLowerCase()) { + case "area" : + return CreateModeTypes.AREA; + case "volume" : + return CreateModeTypes.VOLUME; + default : + return CreateModeTypes.UNDEFINED; + } + } + + @Override + public void serialize(TypeToken<?> type, CreateModeType obj, ConfigurationNode node) throws ObjectMappingException { + node.setValue(obj.getName()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/CustomFlagSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/CustomFlagSerializer.java new file mode 100644 index 0000000..a0808ad --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/CustomFlagSerializer.java @@ -0,0 +1,193 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.permission.flag.CustomFlagData; +import com.griefdefender.permission.flag.GDCustomFlagDefinition; +import com.griefdefender.registry.FlagRegistryModule; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +public class CustomFlagSerializer implements TypeSerializer<GDCustomFlagDefinition> { + + @Override + public GDCustomFlagDefinition deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + final String flagDisplayName = node.getKey().toString(); + final boolean enabled = node.getNode("enabled").getBoolean(); + final String descr = node.getNode("description").getString(); + Component description = TextComponent.empty(); + if (descr != null) { + description = LegacyComponentSerializer.legacy().deserialize(descr, '&'); + } + + List<String> contextList = node.getNode("contexts").getList(TypeToken.of(String.class)); + List<String> permissionList = node.getNode("permissions").getList(TypeToken.of(String.class)); + if (permissionList == null) { + throw new ObjectMappingException("No permissions found for flag definition '" + flagDisplayName + "'. You must specify at least 1 or more permissions."); + } + + List<CustomFlagData> flagDataList = new ArrayList<>(); + for (String permissionEntry : permissionList) { + String permission = permissionEntry.replace(" ", ""); + String[] parts = permission.split(","); + Flag linkedFlag = null; + Set<Context> flagContexts = new HashSet<>(); + for (String part : parts) { + String[] split = part.split("="); + String key = split[0]; + String value = split[1]; + // Handle linked Flag + if (key.equalsIgnoreCase("flag")) { + final String flagName = value; + linkedFlag = FlagRegistryModule.getInstance().getById(flagName).orElse(null); + if (linkedFlag == null) { + throw new ObjectMappingException("Input '" + flagName + "' is not a valid GD flag to link to."); + } + } else { //contexts + // validate context key + switch (key) { + case ContextKeys.SOURCE: + case ContextKeys.TARGET: + if (!value.contains(":") && !value.contains("#")) { + value = "minecraft:" + value; + } + flagContexts.add(new Context(key, value)); + break; + case "used_item": + case "item_name": + case ContextKeys.CLAIM_DEFAULT: + case ContextKeys.CLAIM_OVERRIDE: + case ContextKeys.STATE: + flagContexts.add(new Context(key, value)); + break; + default: + throw new ObjectMappingException("Invalid context '" + key + "' with value '" + value + "'."); + } + } + } + if (linkedFlag == null) { + throw new ObjectMappingException("No linked flag specified. You need to specify 'flag=<flagname>'."); + } + + flagDataList.add(new CustomFlagData(linkedFlag, flagContexts)); + } + final GDCustomFlagDefinition flagDefinition = new GDCustomFlagDefinition(flagDataList, flagDisplayName, description); + flagDefinition.setIsEnabled(enabled); + Set<Context> contexts = new HashSet<>(); + if (contextList != null) { + for (String context : contextList) { + final String parts[] = context.split("="); + if (parts.length <= 1) { + throw new ObjectMappingException("Invalid context '" + context + "' for flag definition '" + flagDisplayName + "'. Skipping..."); + } + final String key = parts[0]; + final String value = parts[1]; + if (key.equalsIgnoreCase("default") || key.equalsIgnoreCase("gd_claim_default")) { + if (!value.equalsIgnoreCase("global") && !value.equalsIgnoreCase("basic") && !value.equalsIgnoreCase("admin") + && !value.equalsIgnoreCase("subdivision") && !value.equalsIgnoreCase("town")) { + throw new ObjectMappingException("Invalid context '" + key + "' with value '" + value + "'."); + } + contexts.add(new Context("gd_claim_default", value)); + } else if (key.equalsIgnoreCase("override") || key.equalsIgnoreCase("gd_claim_override")) { + if (!value.equalsIgnoreCase("global") && !value.equalsIgnoreCase("basic") && !value.equalsIgnoreCase("admin") + && !value.equalsIgnoreCase("subdivision") && !value.equalsIgnoreCase("town")) { + // try UUID + if (value.length() == 36) { + UUID uuid = null; + try { + uuid = UUID.fromString(value); + } catch (IllegalArgumentException e) { + throw new ObjectMappingException("Invalid context '" + key + "' with value '" + value + "'."); + } + } else { + throw new ObjectMappingException("Invalid context '" + key + "' with value '" + value + "'."); + } + } + contexts.add(new Context("gd_claim_override", value)); + } else { + contexts.add(new Context(key, value)); + } + } + flagDefinition.setDefinitionContexts(contexts); + } + return flagDefinition; + } + + @Override + public void serialize(TypeToken<?> type, GDCustomFlagDefinition obj, ConfigurationNode node) throws ObjectMappingException { + node.getNode("enabled").setValue(obj.isEnabled()); + String description = ""; + if (obj.getDescription() != TextComponent.empty()) { + description = LegacyComponentSerializer.legacy().serialize((Component) obj.getDescription(), '&'); + node.getNode("description").setValue(description); + } + + if (!obj.getDefinitionContexts().isEmpty()) { + List<String> contextList = new ArrayList<>(); + ConfigurationNode contextNode = node.getNode("contexts"); + for (Context context : obj.getDefinitionContexts()) { + contextList.add(context.getKey().toLowerCase() + "=" + context.getValue().toLowerCase()); + } + contextNode.setValue(contextList); + } + ConfigurationNode permissionNode = node.getNode("permissions"); + List<String> permissions = new ArrayList<>(); + for (CustomFlagData flagData : obj.getFlagData()) { + int count = 0; + final Flag flag = flagData.getFlag(); + final Set<Context> dataContexts = flagData.getContexts(); + String permission = ""; + if (count > 0) { + permission += ", "; + } + permission += "flag=" + flag.getName().toLowerCase(); + count++; + + for (Context context : dataContexts) { + String key = context.getKey(); + permission += ", " + key + "=" + context.getValue(); + } + + permissions.add(permission); + } + permissionNode.setValue(permissions); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/GameModeTypeSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/GameModeTypeSerializer.java new file mode 100644 index 0000000..72fd7e4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/GameModeTypeSerializer.java @@ -0,0 +1,58 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.GameModeTypes; + +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +public class GameModeTypeSerializer implements TypeSerializer<GameModeType> { + + @Override + public GameModeType deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + switch(node.getString().toLowerCase()) { + case "adventure" : + return GameModeTypes.ADVENTURE; + case "creative" : + return GameModeTypes.CREATIVE; + case "spectator" : + return GameModeTypes.SPECTATOR; + case "survival" : + return GameModeTypes.SURVIVAL; + default : + return GameModeTypes.UNDEFINED; + } + } + + @Override + public void serialize(TypeToken<?> type, GameModeType obj, ConfigurationNode node) throws ObjectMappingException { + node.setValue(obj.getName()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/serializer/WeatherTypeSerializer.java b/sponge/src/main/java/com/griefdefender/configuration/serializer/WeatherTypeSerializer.java new file mode 100644 index 0000000..6151a57 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/serializer/WeatherTypeSerializer.java @@ -0,0 +1,54 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.serializer; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.api.permission.option.type.WeatherTypes; + +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer; + +public class WeatherTypeSerializer implements TypeSerializer<WeatherType> { + + @Override + public WeatherType deserialize(TypeToken<?> type, ConfigurationNode node) throws ObjectMappingException { + switch (node.getString().toLowerCase()) { + case "clear" : + return WeatherTypes.CLEAR; + case "rain" : + return WeatherTypes.RAIN; + default : + return WeatherTypes.UNDEFINED; + } + } + + @Override + public void serialize(TypeToken<?> type, WeatherType obj, ConfigurationNode node) throws ObjectMappingException { + node.setValue(obj.getName()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/configuration/type/ConfigBase.java b/sponge/src/main/java/com/griefdefender/configuration/type/ConfigBase.java new file mode 100644 index 0000000..7888462 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/type/ConfigBase.java @@ -0,0 +1,71 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.type; + +import com.griefdefender.configuration.category.BanCategory; +import com.griefdefender.configuration.category.BlacklistCategory; +import com.griefdefender.configuration.category.ClaimCategory; +import com.griefdefender.configuration.category.GeneralCategory; +import com.griefdefender.configuration.category.OptionCategory; +import com.griefdefender.configuration.category.PvpCategory; +import com.griefdefender.configuration.category.TownCategory; +import com.griefdefender.configuration.category.VisualCategory; + +import ninja.leaping.configurate.objectmapping.Setting; + +public class ConfigBase { + + @Setting(value = "bans", comment = "Controls which item/block/entity id's are banned globally from all events. " + + "\nNote: Id's support wildcards '?' and '*' by using Apache's wildcard matcher." + + "\nThe wildcard '?' represents a single character." + + "\nThe wildcard '*' represents zero or more characters." + + "\nFor more information on usage, see https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/FilenameUtils.html#wildcardMatch(java.lang.String,%20java.lang.String)") + public BanCategory bans = new BanCategory(); + + @Setting(value = "blacklist", comment = "Controls which item/block/entity id's are blacklisted from events either on a per-flag basis or globally. " + + "\nNote: Id's support wildcards '?' and '*' by using Apache's wildcard matcher." + + "\nThe wildcard '?' represents a single character." + + "\nThe wildcard '*' represents zero or more characters." + + "\nFor more information on usage, see https://commons.apache.org/proper/commons-io/javadocs/api-2.5/org/apache/commons/io/FilenameUtils.html#wildcardMatch(java.lang.String,%20java.lang.String)") + public BlacklistCategory blacklist = new BlacklistCategory(); + + @Setting + public ClaimCategory claim = new ClaimCategory(); + + @Setting + public OptionCategory options = new OptionCategory(); + + @Setting + public GeneralCategory general = new GeneralCategory(); + + @Setting + public PvpCategory pvp = new PvpCategory(); + + @Setting + public TownCategory town = new TownCategory(); + + @Setting + public VisualCategory visual = new VisualCategory(); +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/configuration/type/GlobalConfig.java b/sponge/src/main/java/com/griefdefender/configuration/type/GlobalConfig.java new file mode 100644 index 0000000..038627c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/configuration/type/GlobalConfig.java @@ -0,0 +1,85 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.configuration.type; + +import com.griefdefender.configuration.category.CustomFlagGroupDefinitionCategory; +import com.griefdefender.configuration.category.DefaultPermissionCategory; +import com.griefdefender.configuration.category.EconomyCategory; +import com.griefdefender.configuration.category.MessageCategory; +import com.griefdefender.configuration.category.MigratorCategory; +import com.griefdefender.configuration.category.ModuleCategory; +import com.griefdefender.configuration.category.PlayerDataCategory; +import com.griefdefender.configuration.category.ThreadCategory; +import com.griefdefender.configuration.category.TownCategory; +import ninja.leaping.configurate.objectmapping.Setting; + +public class GlobalConfig extends ConfigBase { + + @Setting(value = "custom-flags", comment = "Used to define a group of custom flags for players/admins." + + "\nEach group defined will be displayed in the flag GUI for users." + + "\nGroups can have the following settings : " + + "\n enabled=<true|false>: Whether the group is enabled." + + "\n admin-group=<true|false>: Whether this group is considered for admin use only." + + "\n hover=<text>: The hover text to be displayed when hovering over group name in GUI." + + "\n title=<text>: The title text to be used for TAB display." + + "\n value=<true|false>: This is used to set a default value for the flag definition. It is only used in conjunction with 'override=<type>, default=<type> settings." + + "\n contexts=[\"key=value\"]: A list of optional definition contexts that will be applied to all permissions." + + "\nNote: This is primary used with 'default' and 'override' contexts. Ex. contexts=[\"default=global\"]" + + "\nEach group will have an associated permission in order to be viewable." + + "\nThe 'user' group will use the permission : 'griefdefender.custom.flag.group.user'" + + "\nThe 'admin' group will use the permission : 'griefdefender.custom.flag.group.admin'" + + "\nWithin each group, you can define flag definitions." + + "\nEach flag definition must be defined in the following format:" + + "\nenabled: Controls whether the definition is enabled. Accepts a value of 'true' or 'false'" + + "\ndescription: The flag description to display on hover. Uses the legacy text format." + + "\npermissions: The list of permissions to link to definition. Each permission accepts the following contexts :" + + "\n flag=<linked-flag>: This context is used to link the permission to a GD specific flag. Ex. 'flag=block-break' would link permission to GD's block-break flag" + + "\n source=<id>: This context is used to specify a source id such as 'minecraft:creeper'." + + "\n target=<id>: This context is used to specify a target id such as 'minecraft:chest'." + + "\n state=<properties>: This context is used to specify a blockstate property such as 'state=lit:true'." + + "\nNote: Required if no source or target context is specified, the permission will default to ALL." + + "\nNote: Available contexts are : flag, source, target, state, used_item, item_name" + + "\nThese contexts may change, See https://github.com/bloodmc/GriefDefender/wiki for latest information.") + public CustomFlagGroupDefinitionCategory customFlags = new CustomFlagGroupDefinitionCategory(); + @Setting + public EconomyCategory economy = new EconomyCategory(); + @Setting + public PlayerDataCategory playerdata = new PlayerDataCategory(); + @Setting + public MessageCategory message = new MessageCategory(); + @Setting(comment = + "List of migrators that convert old or other protection data into the current GD claim data format." + + "\nNote: It is recommended to backup data before using.") + public MigratorCategory migrator = new MigratorCategory(); + @Setting(value = "modules") + public ModuleCategory modules = new ModuleCategory(); + @Setting(value = "default-permissions") + public DefaultPermissionCategory permissionCategory = new DefaultPermissionCategory(); + @Setting + public ThreadCategory thread = new ThreadCategory(); + + @Setting + public TownCategory town = new TownCategory(); +} diff --git a/sponge/src/main/java/com/griefdefender/economy/GDBankTransaction.java b/sponge/src/main/java/com/griefdefender/economy/GDBankTransaction.java new file mode 100644 index 0000000..7b98b5b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/economy/GDBankTransaction.java @@ -0,0 +1,125 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.economy; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.economy.BankTransaction; +import com.griefdefender.api.economy.BankTransactionType; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +public class GDBankTransaction implements BankTransaction { + + public final BankTransactionType type; + public final UUID source; + public final Instant timestamp; + public final double amount; + + public GDBankTransaction(Claim claim, BankTransactionType type, Instant timestamp, double amount) { + this(type, timestamp, amount); + } + + public GDBankTransaction(BankTransactionType type, UUID source, Instant timestamp, double amount) { + this.type = type; + this.timestamp = timestamp; + this.amount = amount; + this.source = source; + } + + public GDBankTransaction(BankTransactionType type, Instant timestamp, double amount) { + this.type = type; + this.timestamp = timestamp; + this.amount = amount; + this.source = null; + } + + @Override + public BankTransactionType getType() { + return this.type; + } + + @Override + public Optional<UUID> getSource() { + return Optional.ofNullable(this.source); + } + + @Override + public Instant getTimestamp() { + return this.timestamp; + } + + @Override + public Double getAmount() { + return this.amount; + } + + public static class BankTransactionBuilder implements Builder { + + private UUID source; + private double amount; + private BankTransactionType type; + + @Override + public Builder type(BankTransactionType type) { + this.type = type; + return this; + } + + @Override + public Builder source(UUID source) { + this.source = source; + return this; + } + + @Override + public Builder amount(double amount) { + this.amount = amount; + return this; + } + + @Override + public Builder reset() { + this.source = null; + this.amount = 0; + this.type = null; + return this; + } + + @Override + public BankTransaction build() { + checkNotNull(this.type); + checkNotNull(this.amount); + if (this.source != null) { + return new GDBankTransaction(this.type, this.source, Instant.now(), this.amount); + } + return new GDBankTransaction(this.type, Instant.now(), this.amount); + } + + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDAttackPlayerEvent.java b/sponge/src/main/java/com/griefdefender/event/GDAttackPlayerEvent.java new file mode 100644 index 0000000..789daea --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDAttackPlayerEvent.java @@ -0,0 +1,45 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.User; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.AttackPlayerEvent; + +import java.util.UUID; + +public class GDAttackPlayerEvent extends GDClaimEvent implements AttackPlayerEvent { + + private User user; + + public GDAttackPlayerEvent(Claim claim, User user) { + super(claim); + } + + @Override + public User getUser() { + return this.user; + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDBorderClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDBorderClaimEvent.java new file mode 100644 index 0000000..000f351 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDBorderClaimEvent.java @@ -0,0 +1,125 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.ChatType; +import com.griefdefender.api.ChatTypes; +import com.griefdefender.api.User; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.BorderClaimEvent; +import com.griefdefender.cache.PermissionHolderCache; + +import net.kyori.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.Optional; +import java.util.UUID; + +public class GDBorderClaimEvent extends GDClaimEvent implements BorderClaimEvent { + + private final User user; + private final Entity entity; + private final UUID uniqueId; + private final Claim exitClaim; + private Component enterMessage; + private Component exitMessage; + private ChatType enterChatType = ChatTypes.CHAT; + private ChatType exitChatType = ChatTypes.CHAT; + + public GDBorderClaimEvent(Entity entity, Claim exit, Claim enter) { + super(enter); + this.user = entity instanceof Player ? PermissionHolderCache.getInstance().getOrCreateUser((Player) entity) : null; + this.entity = entity; + this.uniqueId = entity.getUniqueId(); + this.exitClaim = exit; + final int defaultChatType = GriefDefenderPlugin.getGlobalConfig().getConfig().message.enterExitChatType; + if (defaultChatType == 1) { + this.enterChatType = ChatTypes.ACTION_BAR; + this.exitChatType = ChatTypes.ACTION_BAR; + } + } + + @Override + public Claim getExitClaim() { + return this.exitClaim; + } + + public Entity getEntity() { + return this.entity; + } + + @Override + public UUID getEntityUniqueId() { + return this.uniqueId; + } + + @Override + public Optional<User> getUser() { + return Optional.ofNullable(this.user); + } + + @Override + public Optional<Component> getEnterMessage() { + if (this.enterMessage == null) { + return this.getClaim().getData().getGreeting(); + } + + return Optional.of(this.enterMessage); + } + + @Override + public Optional<Component> getExitMessage() { + if (this.exitMessage == null) { + return this.exitClaim.getData().getFarewell(); + } + + return Optional.of(this.exitMessage); + } + + @Override + public void setEnterMessage(@Nullable Component message, ChatType chatType) { + this.enterMessage = message; + this.enterChatType = chatType; + } + + @Override + public void setExitMessage(@Nullable Component message, ChatType chatType) { + this.exitMessage = message; + this.exitChatType = chatType; + } + + @Override + public ChatType getExitMessageChatType() { + return exitChatType; + } + + @Override + public ChatType getEnterMessageChatType() { + return enterChatType; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/event/GDCauseStackManager.java b/sponge/src/main/java/com/griefdefender/event/GDCauseStackManager.java new file mode 100644 index 0000000..f84f4be --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDCauseStackManager.java @@ -0,0 +1,97 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Queues; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.event.EventCause; +import com.griefdefender.cache.PermissionHolderCache; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.User; + +import java.util.Deque; + +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class GDCauseStackManager { + + private int tick_stored; + + private static GDCauseStackManager instance; + + private final Deque<Object> cause = Queues.newArrayDeque(); + + @Nullable private EventCause cached_cause; + + public EventCause getCurrentCause() { + if (Sponge.getServer().getRunningTimeTicks() != tick_stored) { + this.cached_cause = null; + this.cause.clear(); + } + if (this.cached_cause == null) { + if (this.cause.isEmpty()) { + this.cached_cause = EventCause.of(GriefDefenderPlugin.getInstance()); + } else { + this.cached_cause = EventCause.of(this.cause); + } + } + return this.cached_cause; + } + + public GDCauseStackManager pushCause(Object obj) { + checkNotNull(obj, "obj"); + if (obj instanceof User) { + obj = PermissionHolderCache.getInstance().getOrCreateUser((User) obj); + } + if (tick_stored == Sponge.getServer().getRunningTimeTicks()) { + this.cause.push(obj); + return this; + } + + this.cached_cause = null; + this.cause.push(obj); + tick_stored = Sponge.getServer().getRunningTimeTicks(); + return this; + } + + public Object popCause() { + this.cached_cause = null; + return this.cause.pop(); + } + + public Object peekCause() { + return this.cause.peek(); + } + + public static GDCauseStackManager getInstance() { + return instance; + } + + static { + instance = new GDCauseStackManager(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDChangeClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDChangeClaimEvent.java new file mode 100644 index 0000000..cefcd07 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDChangeClaimEvent.java @@ -0,0 +1,79 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.event.ChangeClaimEvent; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +public class GDChangeClaimEvent extends GDClaimEvent implements ChangeClaimEvent { + + public GDChangeClaimEvent(Claim claim) { + super(claim); + } + + public static class Type extends GDChangeClaimEvent implements ChangeClaimEvent.Type { + private final ClaimType originalType; + private final ClaimType newType; + + public Type(Claim claim, ClaimType newType) { + super(claim); + this.originalType = claim.getType(); + this.newType = newType; + } + + @Override + public ClaimType getOriginalType() { + return originalType; + } + + @Override + public ClaimType getType() { + return newType; + } + } + + public static class Resize extends GDChangeClaimEvent implements ChangeClaimEvent.Resize { + private Vector3i startCorner; + private Vector3i endCorner; + + public Resize(Claim claim, Location<World> startCorner, Location<World> endCorner) { + super(claim); + } + + @Override + public Vector3i getStartCorner() { + return this.startCorner; + } + + @Override + public Vector3i getEndCorner() { + return this.endCorner; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDClaimEvent.java new file mode 100644 index 0000000..08cf739 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDClaimEvent.java @@ -0,0 +1,82 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.google.common.collect.ImmutableList; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.ClaimEvent; +import com.griefdefender.api.event.EventCause; +import net.kyori.text.Component; +import org.spongepowered.api.Sponge; + +import java.util.List; +import java.util.Optional; + +public class GDClaimEvent implements ClaimEvent { + + private List<Claim> claims; + private Component message; + private boolean isCancelled = false; + private final EventCause cause; + + public GDClaimEvent(Claim claim) { + this.claims = ImmutableList.of(claim); + this.cause = EventCause.of(Sponge.getCauseStackManager().getCurrentCause().all()); + } + + public GDClaimEvent(List<Claim> claims) { + this.claims = ImmutableList.copyOf(claims); + this.cause = EventCause.of(Sponge.getCauseStackManager().getCurrentCause().all()); + } + + @Override + public List<Claim> getClaims() { + return this.claims; + } + + @Override + public void setMessage(Component message) { + this.message = message; + } + + @Override + public Optional<Component> getMessage() { + return Optional.ofNullable(this.message); + } + + public boolean cancelled() { + return this.isCancelled; + } + + public void cancelled(boolean cancel) { + this.isCancelled = cancel; + } + + @Override + public EventCause getCause() { + return this.cause; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDCreateClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDCreateClaimEvent.java new file mode 100644 index 0000000..c6dd960 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDCreateClaimEvent.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.CreateClaimEvent; + +public class GDCreateClaimEvent extends GDClaimEvent implements CreateClaimEvent { + + public GDCreateClaimEvent(Claim claim) { + super(claim); + } + + public static class Pre extends GDCreateClaimEvent { + + public Pre(Claim claim) { + super(claim); + } + } + + public static class Post extends GDCreateClaimEvent { + + public Post(Claim claim) { + super(claim); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDFlagPermissionEvent.java b/sponge/src/main/java/com/griefdefender/event/GDFlagPermissionEvent.java new file mode 100644 index 0000000..9ff71b4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDFlagPermissionEvent.java @@ -0,0 +1,82 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.Subject; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.event.FlagPermissionEvent; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; + +public class GDFlagPermissionEvent extends GDPermissionEvent implements FlagPermissionEvent { + + public GDFlagPermissionEvent(Subject subject) { + super(subject); + } + + public GDFlagPermissionEvent(Subject subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + + public static class ClearAll extends GDFlagPermissionEvent implements FlagPermissionEvent.ClearAll { + + public ClearAll(Subject subject) { + super(subject); + } + + public ClearAll(Subject subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + } + + public static class Clear extends GDFlagPermissionEvent implements FlagPermissionEvent.Clear { + + public Clear(Subject subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + } + + public static class Set extends GDFlagPermissionEvent implements FlagPermissionEvent.Set { + + private final Flag flag; + private final Tristate value; + + public Set(Subject subject, Flag flag, Tristate value, java.util.Set<Context> contexts) { + super(subject, contexts); + this.flag = flag; + this.value = value; + } + + @Override + public Flag getFlag() { + return this.flag; + } + + @Override + public Tristate getValue() { + return this.value; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDGroupTrustClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDGroupTrustClaimEvent.java new file mode 100644 index 0000000..74d6e22 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDGroupTrustClaimEvent.java @@ -0,0 +1,71 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.event.GroupTrustClaimEvent; + +import java.util.List; + +public class GDGroupTrustClaimEvent extends GDTrustClaimEvent implements GroupTrustClaimEvent { + + private List<String> groups; + + public GDGroupTrustClaimEvent(Claim claim, List<String> groups, TrustType trustType) { + super(claim, trustType); + this.groups = groups; + } + + public GDGroupTrustClaimEvent(List<Claim> claims, List<String> groups, TrustType trustType) { + super(claims, trustType); + this.groups = groups; + } + + @Override + public List<String> getGroups() { + return this.groups; + } + + public static class Add extends GDGroupTrustClaimEvent implements GroupTrustClaimEvent.Add { + public Add(List<Claim> claims, List<String> groups, TrustType trustType) { + super(claims, groups, trustType); + } + + public Add(Claim claim, List<String> groups, TrustType trustType) { + super(claim, groups, trustType); + } + } + + public static class Remove extends GDGroupTrustClaimEvent implements GroupTrustClaimEvent.Remove { + public Remove(List<Claim> claims, List<String> groups, TrustType trustType) { + super(claims, groups, trustType); + } + + public Remove(Claim claim, List<String> groups, TrustType trustType) { + super(claim, groups, trustType); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDLoadClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDLoadClaimEvent.java new file mode 100644 index 0000000..87ddb1b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDLoadClaimEvent.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.LoadClaimEvent; + +public class GDLoadClaimEvent extends GDClaimEvent implements LoadClaimEvent { + + public GDLoadClaimEvent(Claim claim) { + super(claim); + } + + public static class Pre extends GDLoadClaimEvent { + + public Pre(Claim claim) { + super(claim); + } + } + + public static class Post extends GDLoadClaimEvent { + + public Post(Claim claim) { + super(claim); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDOptionEvent.java b/sponge/src/main/java/com/griefdefender/event/GDOptionEvent.java new file mode 100644 index 0000000..4e5d59a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDOptionEvent.java @@ -0,0 +1,73 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.event.OptionPermissionEvent; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.permission.GDPermissionHolder; + +public class GDOptionEvent extends GDPermissionEvent implements OptionPermissionEvent { + + public GDOptionEvent(GDPermissionHolder subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + + public static class ClearAll extends GDOptionEvent implements OptionPermissionEvent.ClearAll { + + public ClearAll(GDPermissionHolder subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + } + + public static class Clear extends GDOptionEvent implements OptionPermissionEvent.Clear { + + public Clear(GDPermissionHolder subject, java.util.Set<Context> contexts) { + super(subject, contexts); + } + } + + public static class Set extends GDOptionEvent implements OptionPermissionEvent.Set { + + private final Option option; + private final String value; + + public Set(GDPermissionHolder subject, Option option, String value, java.util.Set<Context> contexts) { + super(subject, contexts); + this.option = option; + this.value = value; + } + + @Override + public Option getOption() { + return this.option; + } + + @Override + public String getValue() { + return this.value; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDPermissionEvent.java b/sponge/src/main/java/com/griefdefender/event/GDPermissionEvent.java new file mode 100644 index 0000000..fc8cb5d --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDPermissionEvent.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import com.griefdefender.api.Subject; +import com.griefdefender.api.event.EventCause; +import com.griefdefender.api.event.PermissionEvent; +import com.griefdefender.api.permission.Context; + +import net.kyori.text.Component; + +public class GDPermissionEvent implements PermissionEvent { + + private final Subject subject; + private Set<Context> contexts; + private Component message; + private boolean isCancelled = false; + + public GDPermissionEvent(Subject subject) { + this.subject = subject; + } + + public GDPermissionEvent(Subject subject, Set<Context> contexts) { + this.subject = subject; + this.contexts = contexts; + } + + @Override + public Subject getSubject() { + return this.subject; + } + + @Override + public Set<Context> getContexts() { + if (this.contexts == null) { + this.contexts = new HashSet<>(); + } + return this.contexts; + } + + @Override + public void setMessage(Component message) { + this.message = message; + } + + @Override + public Optional<Component> getMessage() { + return Optional.ofNullable(this.message); + } + + @Override + public boolean cancelled() { + return this.isCancelled; + } + + @Override + public void cancelled(boolean cancelled) { + this.isCancelled = cancelled; + } + + @Override + public EventCause getCause() { + return GDCauseStackManager.getInstance().getCurrentCause(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDRemoveClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDRemoveClaimEvent.java new file mode 100644 index 0000000..6b7711a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDRemoveClaimEvent.java @@ -0,0 +1,83 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.RemoveClaimEvent; +import com.griefdefender.configuration.category.ClaimCategory; + +import java.util.List; + +public class GDRemoveClaimEvent extends GDClaimEvent implements RemoveClaimEvent { + + private boolean shouldRestore; + + public GDRemoveClaimEvent(Claim claim) { + super(claim); + final ClaimCategory claimCategory = GriefDefenderPlugin.getActiveConfig(claim.getWorldUniqueId()).getConfig().claim; + if (claimCategory.claimAutoNatureRestore || claimCategory.claimAutoSchematicRestore) { + shouldRestore = true; + } else { + shouldRestore = false; + } + } + + public GDRemoveClaimEvent(List<Claim> claims) { + super(claims); + } + + public static class Abandon extends GDRemoveClaimEvent implements RemoveClaimEvent.Abandon { + + public Abandon(Claim claim) { + super(claim); + } + + public Abandon(List<Claim> claims) { + super(claims); + } + } + + public static class Delete extends GDRemoveClaimEvent implements RemoveClaimEvent.Delete { + + public Delete(Claim claim) { + super(claim); + } + + public Delete(List<Claim> claims) { + super(claims); + } + } + + @Override + public boolean isRestoring() { + return shouldRestore; + } + + @Override + public void shouldRestore(boolean restore) { + this.shouldRestore = restore; + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDSaveClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDSaveClaimEvent.java new file mode 100644 index 0000000..11751df --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDSaveClaimEvent.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.SaveClaimEvent; + +public class GDSaveClaimEvent extends GDClaimEvent implements SaveClaimEvent { + + public GDSaveClaimEvent(Claim claim) { + super(claim); + } + + public static class Pre extends GDSaveClaimEvent { + + public Pre(Claim claim) { + super(claim); + } + } + + public static class Post extends GDSaveClaimEvent { + + public Post(Claim claim) { + super(claim); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDTaxClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDTaxClaimEvent.java new file mode 100644 index 0000000..44ee9f4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDTaxClaimEvent.java @@ -0,0 +1,67 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.TaxClaimEvent; + +public class GDTaxClaimEvent extends GDClaimEvent implements TaxClaimEvent { + + private final double originalTaxRate; + private final double originalTaxAmount; + private double taxRate; + + public GDTaxClaimEvent(Claim claim, double rate, double amount) { + super(claim); + this.originalTaxRate = rate; + this.originalTaxAmount = amount; + this.taxRate = rate; + } + + @Override + public double getOriginalTaxRate() { + return this.originalTaxRate; + } + + @Override + public double getOriginalTaxAmount() { + return this.originalTaxAmount; + } + + @Override + public double getTaxRate() { + return this.taxRate; + } + + @Override + public double getTaxAmount() { + return (this.getClaim().getClaimBlocks() / 256) * this.taxRate; + } + + @Override + public void setTaxRate(double newTaxRate) { + this.taxRate = newTaxRate; + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDTransferClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDTransferClaimEvent.java new file mode 100644 index 0000000..61c5d63 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDTransferClaimEvent.java @@ -0,0 +1,57 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.event.TransferClaimEvent; + +import java.util.UUID; + +public class GDTransferClaimEvent extends GDClaimEvent implements TransferClaimEvent { + + private UUID originalOwner; + private UUID newOwner; + + public GDTransferClaimEvent(Claim claim, UUID originalOwner, UUID newOwner) { + super(claim); + this.originalOwner = originalOwner; + this.newOwner = newOwner; + } + + @Override + public UUID getOriginalOwner() { + return this.originalOwner; + } + + @Override + public UUID getNewOwner() { + return this.newOwner; + } + + @Override + public void setNewOwner(UUID newOwner) { + this.newOwner = newOwner; + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDTrustClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDTrustClaimEvent.java new file mode 100644 index 0000000..6eb4896 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDTrustClaimEvent.java @@ -0,0 +1,51 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.event.TrustClaimEvent; + +import java.util.List; + +public class GDTrustClaimEvent extends GDClaimEvent implements TrustClaimEvent { + + private TrustType trustType; + + public GDTrustClaimEvent(Claim claim, TrustType trustType) { + super(claim); + this.trustType = trustType; + } + + public GDTrustClaimEvent(List<Claim> claims, TrustType trustType) { + super(claims); + this.trustType = trustType; + } + + @Override + public TrustType getTrustType() { + return this.trustType; + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/GDUserTrustClaimEvent.java b/sponge/src/main/java/com/griefdefender/event/GDUserTrustClaimEvent.java new file mode 100644 index 0000000..566c8b8 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/GDUserTrustClaimEvent.java @@ -0,0 +1,72 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; + +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.event.UserTrustClaimEvent; + +import java.util.List; +import java.util.UUID; + +public class GDUserTrustClaimEvent extends GDTrustClaimEvent implements UserTrustClaimEvent { + + private List<UUID> users; + + public GDUserTrustClaimEvent(Claim claim, List<UUID> users, TrustType trustType) { + super(claim, trustType); + this.users = users; + } + + public GDUserTrustClaimEvent(List<Claim> claims, List<UUID> users, TrustType trustType) { + super(claims, trustType); + this.users = users; + } + + @Override + public List<UUID> getUsers() { + return this.users; + } + + public static class Add extends GDUserTrustClaimEvent implements UserTrustClaimEvent.Add { + public Add(List<Claim> claims, List<UUID> users, TrustType trustType) { + super(claims, users, trustType); + } + + public Add(Claim claim, List<UUID> users, TrustType trustType) { + super(claim, users, trustType); + } + } + + public static class Remove extends GDUserTrustClaimEvent implements UserTrustClaimEvent.Remove { + public Remove(List<Claim> claims,List<UUID> users, TrustType trustType) { + super(claims, users, trustType); + } + + public Remove(Claim claim, List<UUID> users, TrustType trustType) { + super(claim, users, trustType); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/event/package-info.java b/sponge/src/main/java/com/griefdefender/event/package-info.java new file mode 100644 index 0000000..7c09a8f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/event/package-info.java @@ -0,0 +1,25 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.event; \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/inject/GriefDefenderImplModule.java b/sponge/src/main/java/com/griefdefender/inject/GriefDefenderImplModule.java new file mode 100644 index 0000000..2d5c586 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/inject/GriefDefenderImplModule.java @@ -0,0 +1,62 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.inject; + +import com.google.inject.PrivateModule; +import com.google.inject.binder.AnnotatedBindingBuilder; +import com.griefdefender.GDCore; +import com.griefdefender.GDEventManager; +import com.griefdefender.GDVersion; +import com.griefdefender.api.Core; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Registry; +import com.griefdefender.api.Version; +import com.griefdefender.api.event.EventManager; +import com.griefdefender.api.permission.PermissionManager; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.registry.GDRegistry; + +import javax.annotation.OverridingMethodsMustInvokeSuper; + +public class GriefDefenderImplModule extends PrivateModule { + + @Override + @OverridingMethodsMustInvokeSuper + protected void configure() { + this.bindAndExpose(Core.class).to(GDCore.class); + this.bindAndExpose(EventManager.class).to(GDEventManager.class); + this.bindAndExpose(PermissionManager.class).to(GDPermissionManager.class); + this.bindAndExpose(Registry.class).to(GDRegistry.class); + this.bindAndExpose(Version.class).to(GDVersion.class); + + this.requestStaticInjection(GriefDefender.class); + } + + protected <T> AnnotatedBindingBuilder<T> bindAndExpose(final Class<T> type) { + this.expose(type); + return this.bind(type); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/EntityRemovalListener.java b/sponge/src/main/java/com/griefdefender/internal/EntityRemovalListener.java new file mode 100644 index 0000000..c9e1d87 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/EntityRemovalListener.java @@ -0,0 +1,92 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal; + +import com.griefdefender.internal.util.BlockUtil; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IWorldEventListener; +import net.minecraft.world.World; + +public class EntityRemovalListener implements IWorldEventListener { + + @Override + public void onEntityRemoved(Entity entityIn) { + BlockUtil.ENTITY_BLOCK_CACHE.remove(entityIn.getEntityId()); + } + + @Override + public void notifyBlockUpdate(World worldIn, BlockPos pos, IBlockState oldState, IBlockState newState, int flags) { + } + + @Override + public void notifyLightSet(BlockPos pos) { + } + + @Override + public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2) { + } + + @Override + public void playSoundToAllNearExcept(EntityPlayer player, SoundEvent soundIn, SoundCategory category, double x, double y, double z, float volume, + float pitch) { + } + + @Override + public void playRecord(SoundEvent soundIn, BlockPos pos) { + } + + @Override + public void spawnParticle(int particleID, boolean ignoreRange, double xCoord, double yCoord, double zCoord, double xSpeed, double ySpeed, + double zSpeed, int... parameters) { + } + + @Override + public void spawnParticle(int p_190570_1_, boolean p_190570_2_, boolean p_190570_3_, double p_190570_4_, double p_190570_6_, double p_190570_8_, + double p_190570_10_, double p_190570_12_, double p_190570_14_, int... p_190570_16_) { + } + + @Override + public void onEntityAdded(Entity entityIn) { + + } + + @Override + public void broadcastSound(int soundID, BlockPos pos, int data) { + } + + @Override + public void playEvent(EntityPlayer player, int type, BlockPos blockPosIn, int data) { + } + + @Override + public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress) { + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/NbtDataHelper.java b/sponge/src/main/java/com/griefdefender/internal/NbtDataHelper.java new file mode 100644 index 0000000..a3f63b1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/NbtDataHelper.java @@ -0,0 +1,61 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.service.user.UserStorageService; + +import java.util.Optional; +import java.util.UUID; + +public class NbtDataHelper { + + public static final String FORGE_DATA = "ForgeData"; + public static final String SPONGE_DATA = "SpongeData"; + public static final String SPONGE_ENTITY_CREATOR = "Creator"; + + public static Optional<User> getOwnerOfEntity(net.minecraft.entity.Entity entity) { + NBTTagCompound nbt = new NBTTagCompound(); + entity.writeToNBT(nbt); + if (nbt.hasKey(FORGE_DATA)) { + NBTTagCompound forgeNBT = nbt.getCompoundTag(FORGE_DATA); + if (forgeNBT.hasKey(SPONGE_DATA) && forgeNBT.getCompoundTag(SPONGE_DATA).hasKey(SPONGE_ENTITY_CREATOR)) { + NBTTagCompound creatorNBT = forgeNBT.getCompoundTag(SPONGE_DATA).getCompoundTag(SPONGE_ENTITY_CREATOR); + UUID uuid = new UUID(creatorNBT.getLong("uuid_most"), creatorNBT.getLong("uuid_least")); + // get player if online + EntityPlayer player = entity.world.getPlayerEntityByUUID(uuid); + if (player != null) { + return Optional.of((User) player); + } + // player is not online, get user from storage if one exists + return Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(uuid); + } + } + return Optional.empty(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/ActivePagination.java b/sponge/src/main/java/com/griefdefender/internal/pagination/ActivePagination.java new file mode 100644 index 0000000..8f6677a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/ActivePagination.java @@ -0,0 +1,218 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import com.griefdefender.command.CommandException; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.event.HoverEvent; +import net.kyori.text.format.TextColor; +import net.kyori.text.format.TextDecoration; +import org.spongepowered.api.command.CommandSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +/** + * Holds logic for an active pagination that is occurring. + */ +public abstract class ActivePagination { + + private static final Component SLASH_TEXT = TextComponent.of("/"); + private static final Component DIVIDER_TEXT = TextComponent.of(" "); + private static final Component CONTINUATION_TEXT = TextComponent.of("..."); + private final Supplier<Optional<CommandSource>> src; + private final UUID id = UUID.randomUUID(); + private final Component nextPageText; + private final Component prevPageText; + @Nullable + private final Component title; + @Nullable + private final Component header; + @Nullable + private final Component footer; + private int currentPage; + private final int maxContentLinesPerPage; + protected final GDPaginationCalculator calc; + private final Component padding; + + public ActivePagination(Supplier<Optional<CommandSource>> src, GDPaginationCalculator calc, @Nullable Component title, + @Nullable Component header, @Nullable Component footer, Component padding) { + this.src = src; + this.calc = calc; + this.title = title; + this.header = header; + this.footer = footer; + this.padding = padding; + this.nextPageText = TextComponent.builder("»") + .color(TextColor.BLUE) + .decoration(TextDecoration.UNDERLINED, true) + .clickEvent(ClickEvent.runCommand("/gd:pagination " + this.id.toString() + " next")) + .hoverEvent(HoverEvent.showText(TextComponent.of("/page next"))) + .insertion("/page next") + .build(); + this.prevPageText = TextComponent.builder("«") + .color(TextColor.BLUE) + .decoration(TextDecoration.UNDERLINED, true) + .clickEvent(ClickEvent.runCommand("/gd:pagination " + this.id.toString() + " prev")) + .hoverEvent(HoverEvent.showText(TextComponent.of("/page prev"))) + .insertion("/page prev") + .build(); + int maxContentLinesPerPage = calc.getLinesPerPage(src.get().get()) - 1; + if (title != null) { + maxContentLinesPerPage -= calc.getLines(title); + } + if (header != null) { + maxContentLinesPerPage -= calc.getLines(header); + } + if (footer != null) { + maxContentLinesPerPage -= calc.getLines(footer); + } + this.maxContentLinesPerPage = maxContentLinesPerPage; + } + + public UUID getId() { + return this.id; + } + + protected abstract Iterable<Component> getLines(int page) throws CommandException; + + protected abstract boolean hasPrevious(int page); + + protected abstract boolean hasNext(int page); + + protected abstract int getTotalPages(); + + public void nextPage() throws CommandException { + specificPage(this.currentPage + 1); + } + + public void previousPage() throws CommandException { + specificPage(this.currentPage - 1); + } + + public void currentPage() throws CommandException { + specificPage(this.currentPage); + } + + protected int getCurrentPage() { + return this.currentPage; + } + + protected int getMaxContentLinesPerPage() { + return this.maxContentLinesPerPage; + } + + public void specificPage(int page) throws CommandException { + CommandSource src = this.src.get() + .orElseThrow(() -> new CommandException(TextComponent.of(String.format("Source for pagination %s is no longer active!", getId())))); + this.currentPage = page; + + List<Component> toSend = new ArrayList<>(); + Component title = this.title; + if (title != null) { + toSend.add(title); + } + if (this.header != null) { + toSend.add(this.header); + } + + for (Component line : getLines(page)) { + toSend.add(line); + } + + Component footer = calculateFooter(page); + toSend.add(this.calc.center(footer, this.padding)); + if (this.footer != null) { + toSend.add(this.footer); + } + for (Component component : toSend) { + TextAdapter.sendComponent(src, component); + } + } + + protected Component calculateFooter(int currentPage) { + boolean hasPrevious = hasPrevious(currentPage); + boolean hasNext = hasNext(currentPage); + + TextComponent.Builder ret = null; + if (hasPrevious) { + ret = TextComponent.builder("").append(this.prevPageText).append(DIVIDER_TEXT); + } else { + ret = TextComponent.builder("«").append(DIVIDER_TEXT); + } + boolean needsDiv = false; + int totalPages = getTotalPages(); + if (totalPages > 1) { + ret.append(TextComponent.builder("") + .hoverEvent(HoverEvent.showText(TextComponent.of("/page " + currentPage))) + .clickEvent(ClickEvent.runCommand("/gd:pagination " + this.id + ' ' + currentPage)) + .insertion("/page " + currentPage) + .append(TextComponent.of(String.valueOf(currentPage))).build()) + .append(SLASH_TEXT) + .append(TextComponent.builder("") + .hoverEvent(HoverEvent.showText(TextComponent.of("/page " + totalPages))) + .clickEvent(ClickEvent.runCommand("/gd:pagination " + this.id + ' ' + totalPages)) + .insertion("/page " + totalPages) + .append(TextComponent.of(String.valueOf(totalPages))).build()); + needsDiv = true; + } + if (hasNext) { + if (needsDiv) { + ret.append(DIVIDER_TEXT); + } + ret.append(this.nextPageText); + } else { + if (needsDiv) { + ret.append(DIVIDER_TEXT); + } + ret.append(TextComponent.of("»")); + } + + ret.color(this.padding.color()); + if (this.title != null) { + ret.mergeDecorations(this.title); + } + return ret.build(); + } + + protected void padPage(final List<Component> currentPage, final int currentPageLines, final boolean addContinuation) { + final int maxContentLinesPerPage = getMaxContentLinesPerPage(); + for (int i = currentPageLines; i < maxContentLinesPerPage; i++) { + if (addContinuation && i == maxContentLinesPerPage - 1) { + currentPage.add(CONTINUATION_TEXT); + } else { + currentPage.add(0, TextComponent.empty()); + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationBuilder.java b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationBuilder.java new file mode 100644 index 0000000..833ae9e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationBuilder.java @@ -0,0 +1,137 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableList; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; + +import javax.annotation.Nullable; + +public class GDPaginationBuilder implements PaginationList.Builder { + + @Nullable + private Iterable<Component> contents; + @Nullable + private Component title; + @Nullable + private Component header; + @Nullable + private Component footer; + private Component paginationSpacer = TextComponent.of("="); + private int linesPerPage = 20; + + @Nullable + private PaginationList paginationList; + + @Override + public PaginationList.Builder contents(Iterable<Component> contents) { + checkNotNull(contents, "The contents cannot be null!"); + this.contents = contents; + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder contents(Component... contents) { + checkNotNull(contents, "The contents cannot be null!"); + this.contents = ImmutableList.copyOf(contents); + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder title(@Nullable Component title) { + this.title = title; + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder header(@Nullable Component header) { + this.header = header; + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder footer(@Nullable Component footer) { + this.footer = footer; + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder padding(Component padding) { + checkNotNull(padding, "The padding cannot be null!"); + this.paginationSpacer = padding; + this.paginationList = null; + return this; + } + + @Override + public PaginationList.Builder linesPerPage(int linesPerPage) { + this.linesPerPage = linesPerPage; + return this; + } + + @Override + public PaginationList build() { + checkState(this.contents != null, "The contents of the pagination list cannot be null!"); + + if (this.paginationList == null) { + this.paginationList = new GDPaginationList(this.contents, this.title, this.header, this.footer, this.paginationSpacer, this.linesPerPage); + } + return this.paginationList; + } + + //@Override + public PaginationList.Builder from(PaginationList list) { + this.reset(); + this.contents = list.getContents(); + this.title = list.getTitle().orElse(null); + this.header = list.getHeader().orElse(null); + this.footer = list.getFooter().orElse(null); + this.paginationSpacer = list.getPadding(); + + this.paginationList = null; + return this; + } + + //@Override + public PaginationList.Builder reset() { + this.contents = null; + this.title = null; + this.header = null; + this.footer = null; + this.paginationSpacer = TextComponent.of("="); + + this.paginationList = null; + return this; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationCalculator.java b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationCalculator.java new file mode 100644 index 0000000..356fa02 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationCalculator.java @@ -0,0 +1,328 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import com.flowpowered.math.GenericMath; +import com.google.common.annotations.VisibleForTesting; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import net.minecraft.util.text.ITextComponent; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextComponentTranslation; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; +import ninja.leaping.configurate.loader.HeaderMode; +import org.spongepowered.api.command.CommandSource; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.PrimitiveIterator; + +/** + * Pagination calculator for players. Handles calculation of text widths, + * centering text, adding padding, adding spacing, and more. + */ +public class GDPaginationCalculator { + + private static final String NON_UNICODE_CHARS; + private static final int[] NON_UNICODE_CHAR_WIDTHS; + private static final byte[] UNICODE_CHAR_WIDTHS; + private static final int LINE_WIDTH = 319; + + private final int linesPerPage; + + /** + * Constructs a new pagination calculator. + * + * @param linesPerPage The amount of lines per page there should be + */ + public GDPaginationCalculator(int linesPerPage) { + this.linesPerPage = linesPerPage; + } + + static { + ConfigurationLoader<CommentedConfigurationNode> loader = HoconConfigurationLoader.builder() + .setURL(GDPaginationCalculator.class.getResource("font-sizes.json")) + .setHeaderMode(HeaderMode.NONE) + .build(); + try { + ConfigurationNode node = loader.load(); + NON_UNICODE_CHARS = node.getNode("non-unicode").getString(); + List<? extends ConfigurationNode> charWidths = node.getNode("char-widths").getChildrenList(); + int[] nonUnicodeCharWidths = new int[charWidths.size()]; + for (int i = 0; i < nonUnicodeCharWidths.length; ++i) { + nonUnicodeCharWidths[i] = charWidths.get(i).getInt(); + } + NON_UNICODE_CHAR_WIDTHS = nonUnicodeCharWidths; + + List<? extends ConfigurationNode> glyphWidths = node.getNode("glyph-widths").getChildrenList(); + byte[] unicodeCharWidths = new byte[glyphWidths.size()]; + for (int i = 0; i < unicodeCharWidths.length; ++i) { + unicodeCharWidths[i] = (byte) glyphWidths.get(i).getInt(); + } + UNICODE_CHAR_WIDTHS = unicodeCharWidths; + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } + } + + int getLinesPerPage(CommandSource source) { + return this.linesPerPage; + } + + /** + * Gets the number of lines the specified text flows into. + * + * @param text The text to calculate the number of lines for + * @return The number of lines that this text flows into + */ + int getLines(Component text) { + //TODO: this needs fixing as well. + return (int) Math.ceil((double) this.getWidth(text) / LINE_WIDTH); + } + + /** + * Gets the width of a character with the specified code + * point, accounting for if its text is bold our not. + * + * @param codePoint The code point of the character + * @param isBold Whether or not the character is bold or not + * @return The width of the character at the code point + */ + @VisibleForTesting + int getWidth(int codePoint, boolean isBold) { + int nonUnicodeIdx = NON_UNICODE_CHARS.indexOf(codePoint); + int width; + if (codePoint == 32) { + width = 4; + } else if (codePoint > 0 && nonUnicodeIdx != -1) { + width = NON_UNICODE_CHAR_WIDTHS[nonUnicodeIdx]; + } else if (UNICODE_CHAR_WIDTHS[codePoint] != 0) { + //from 1.9 & 255 to avoid strange signed int math ruining things. + //https://bugs.mojang.com/browse/MC-7181 + final int temp = UNICODE_CHAR_WIDTHS[codePoint] & 255; + // Split into high and low nibbles. + //bit digits + //87654321 >>> 4 = 00008765 + int startColumn = temp >>> 4; + //87654321 & 00001111 = 00004321 + int endColumn = temp & 15; + + width = (endColumn + 1) - startColumn; + //Why does this scaling happen? + //I believe it makes unicode fonts skinnier to better match the character widths of the default Minecraft + // font however there is a int math vs float math bug in the Minecraft FontRenderer. + //The float math is adjusted for rendering, they attempt to do the same thing for calculating string widths + //using integer math, this has potential rounding errors, but we should copy it and use ints as well. + width = (width / 2) + 1; + } else { + width = 0; + } + //if bolded width gets 1 added. + if(isBold && width > 0) { + width = width + 1; + } + + return width; + } + + /** + * Calculates the width of a given text as the number of character + * pixels/columns the line takes up. + * + * @param text The text to get the width of + * @return The amount of character pixels/columns the text takes up + */ + @VisibleForTesting + int getWidth(Component text) { + final ITextComponent internal = ITextComponent.Serializer.jsonToComponent(GsonComponentSerializer.INSTANCE.serialize(text)); + Iterable<ITextComponent> children = new TextComponentIterable(internal, true); + int total = 0; + + for(ITextComponent child : children) { + PrimitiveIterator.OfInt i_it; + if(child instanceof TextComponentString || child instanceof TextComponentTranslation) { + i_it = child.getUnformattedComponentText().codePoints().iterator(); + } else { + continue; + } + + boolean bold = child.getStyle().getBold(); + + Integer cp; + boolean newLine = false; + while(i_it.hasNext()){ + cp = i_it.next(); + if (cp == '\n') { + // if the previous character is a '\n' + if (newLine) { + total += LINE_WIDTH; + } else { + total = ((int) Math.ceil((double) total / LINE_WIDTH)) * LINE_WIDTH; + newLine = true; + } + } else { + int width = getWidth(cp, bold); + total += width; + newLine = false; + } + } + } + + return total; + } + + /** + * Centers a text within the middle of the chat box. + * + * <p>Generally used for titles and footers.</p> + * + * <p>To use no heading, just pass in a 0 width text for + * the first argument.</p> + * + * @param text The text to center + * @param padding A padding character with a width >1 + * @return The centered text, or if too big, the original text + */ + //TODO: Probably should completely rewrite this to not compute padding, but loop until the padding is done, unless + //we can get accurate computation of padding ahead of time. + Component center(Component text, Component padding) { + int inputLength = getWidth(text); + //Minecraft breaks lines when the next character would be > then LINE_WIDTH, this seems most graceful way to fail + if (inputLength >= LINE_WIDTH) { + return text; + } + final Component textWithSpaces = addSpaces(TextComponent.of(" "), text); + + //Minecraft breaks lines when the next character would be > then LINE_WIDTH + boolean addSpaces = getWidth(textWithSpaces) <= LINE_WIDTH; + + //TODO: suspect, why are we changing the style of the padding, they may want different styles on the padding. + Component styledPadding = withStyle(padding, text); + int paddingLength = getWidth(styledPadding); + final TextComponent.Builder output = TextComponent.builder(""); + + //Using 0 width unicode symbols as padding throws us into an unending loop, replace them with the default padding + if(paddingLength < 1) { + padding = TextComponent.of("="); + styledPadding = withColor(withStyle(padding, text), text); + paddingLength = getWidth(styledPadding); + } + + //if we only need padding + if (inputLength == 0) { + addPadding(padding, output, GenericMath.floor((double) LINE_WIDTH / paddingLength)); + } else { + if(addSpaces) { + text = textWithSpaces; + inputLength = getWidth(textWithSpaces); + } + + int paddingNecessary = LINE_WIDTH - inputLength; + + int paddingCount = GenericMath.floor(paddingNecessary / paddingLength); + //pick a halfway point + int beforePadding = GenericMath.floor(paddingCount / 2.0); + //Do not use ceil, this prevents floating point errors. + int afterPadding = paddingCount - beforePadding; + + addPadding(styledPadding, output, beforePadding); + output.append(text); + addPadding(styledPadding, output, afterPadding); + } + + return this.finalizeBuilder(text, output); + } + + /** + * Gives the first text argument the style of the second. + * + * @param text The text to stylize + * @param styled The styled text + * @return The original text now stylized + */ + private Component withStyle(Component text, Component styled) { + return ((TextComponent) text).toBuilder().mergeStyle(styled).build(); + } + + /** + * Gives the first text argument the color of the second. + * + * @param text The text to color + * @param colored The colored text + * @return The original text now colored + */ + private Component withColor(Component text, Component colored) { + return ((TextComponent) text).toBuilder().color(colored.color()).build(); + } + + /** + * Finalizes the builder used in centering text. + * + * @param text The text to get the style from + * @param build The work in progress text builder + * @return The finalized, properly styled text. + */ + private Component finalizeBuilder(Component text, TextComponent.Builder build) { + return build.mergeDecorations(text).build(); + } + + /** + * Adds spaces to both sides of the specified text. + * + * <p>Overrides all color and style with the + * text's color and style.</p> + * + * @param spaces The spaces to use + * @param text The text to add to + * @return The text with the added spaces + */ + private Component addSpaces(Component spaces, Component text) { + return ((TextComponent) spaces).toBuilder() + .append(text) + .append(spaces) + .color(text.color()) + .mergeDecorations(text) + .build(); + } + + /** + * Adds the specified padding text to a piece of text being built + * up to a certain amount specified by a count. + * + * @param padding The padding text to use + * @param build The work in progress text to add to + * @param count The amount of padding to add + */ + private void addPadding(Component padding, TextComponent.Builder build, int count) { + if (count > 0) { + build.append(Collections.nCopies(count, padding)); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationHolder.java b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationHolder.java new file mode 100644 index 0000000..d0ec062 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationHolder.java @@ -0,0 +1,138 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.MapMaker; +import com.griefdefender.command.CommandException; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.text.channel.MessageReceiver; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nullable; + +public class GDPaginationHolder { + + private static final GDPaginationHolder INSTANCE = new GDPaginationHolder(); + + private final ConcurrentMap<MessageReceiver, SourcePaginations> activePaginations = new MapMaker().weakKeys().makeMap(); + + // We have a second active pagination system because of the way Players are handled by the server. + // As Players are recreated every time they die in game, just storing the player in a weak map will + // cause the player to be removed form the map upon death. Thus, player paginations get redirected + // through to this cache instead, which last for 10 minutes from last access. + private final Cache<UUID, SourcePaginations> playerActivePaginations = Caffeine.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + public ActivePagination getActivePagination(CommandSource src, String id) throws CommandException { + SourcePaginations paginations = GDPaginationHolder.getInstance().getPaginationState(src, false); + if (paginations == null) { + return null; + } + + UUID pageId = null; + try { + pageId = UUID.fromString(id); + } catch (IllegalArgumentException ex) { + throw new CommandException(TextComponent.of("Input was not a valid UUID!", TextColor.RED)); + } + + ActivePagination pagination = paginations.get(pageId); + if (pagination == null) { + throw new CommandException(TextComponent.of("No pagination registered for id " + id.toString(), TextColor.RED)); + } + return pagination; + } + + @Nullable + SourcePaginations getPaginationState(MessageReceiver source, boolean create) { + if (source instanceof Player) { + return getPaginationStateForPlayer((Player) source, create); + } + + return getPaginationStateForNonPlayer(source, create); + } + + @Nullable + SourcePaginations getPaginationStateForNonPlayer(MessageReceiver source, boolean create) { + SourcePaginations ret = this.activePaginations.get(source); + if (ret == null && create) { + ret = new SourcePaginations(); + SourcePaginations existing = this.activePaginations.putIfAbsent(source, ret); + if (existing != null) { + ret = existing; + } + } + return ret; + } + + @Nullable + SourcePaginations getPaginationStateForPlayer(Player source, boolean create) { + return this.playerActivePaginations.get(source.getUniqueId(), k -> create ? new SourcePaginations() : null); + } + + static class SourcePaginations { + private final Map<UUID, ActivePagination> paginations = new ConcurrentHashMap<>(); + @Nullable private volatile UUID lastUuid; + + @Nullable public ActivePagination get(UUID uuid) { + return this.paginations.get(uuid); + } + + public void put(ActivePagination pagination) { + synchronized (this.paginations) { + this.paginations.put(pagination.getId(), pagination); + this.lastUuid = pagination.getId(); + } + } + + public Set<UUID> keys() { + return this.paginations.keySet(); + } + + @Nullable + public UUID getLastUuid() { + return this.lastUuid; + } + } + + public static GDPaginationHolder getInstance() { + return INSTANCE; + } + + public Cache<UUID, SourcePaginations> getActivePaginationCache() { + return this.playerActivePaginations; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationList.java b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationList.java new file mode 100644 index 0000000..a883f13 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/GDPaginationList.java @@ -0,0 +1,145 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; +import com.griefdefender.command.CommandException; +import net.kyori.text.Component; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.command.source.ProxySource; +import org.spongepowered.api.entity.living.player.Player; + +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import javax.annotation.Nullable; + +public class GDPaginationList implements PaginationList { + + private final Iterable<Component> contents; + private final Optional<Component> title; + private final Optional<Component> header; + private final Optional<Component> footer; + private final Component paginationSpacer; + private final int linesPerPage; + + public GDPaginationList(Iterable<Component> contents, @Nullable Component title, @Nullable Component header, + @Nullable Component footer, Component paginationSpacer, int linesPerPage) { + this.contents = contents; + this.title = Optional.ofNullable(title); + this.header = Optional.ofNullable(header); + this.footer = Optional.ofNullable(footer); + this.paginationSpacer = paginationSpacer; + this.linesPerPage = linesPerPage; + } + + @Override + public Iterable<Component> getContents() { + return this.contents; + } + + @Override + public Optional<Component> getTitle() { + return this.title; + } + + @Override + public Optional<Component> getHeader() { + return this.header; + } + + @Override + public Optional<Component> getFooter() { + return this.footer; + } + + @Override + public Component getPadding() { + return this.paginationSpacer; + } + + @Override + public int getLinesPerPage() { + return this.linesPerPage; + } + + @Override + public void sendTo(final CommandSource receiver, int page) { + checkNotNull(receiver, "The message receiver cannot be null!"); + + CommandSource realSource = receiver; + while (realSource instanceof ProxySource) { + realSource = ((ProxySource)realSource).getOriginalSource(); + } + final GDPaginationCalculator calculator = new GDPaginationCalculator(this.linesPerPage); + Iterable<Map.Entry<Component, Integer>> counts = StreamSupport.stream(this.contents.spliterator(), false).map(input -> { + int lines = calculator.getLines(input); + return Maps.immutableEntry(input, lines); + }).collect(Collectors.toList()); + + Component title = this.title.orElse(null); + if (title != null) { + title = calculator.center(title, this.paginationSpacer); + } + + // If the MessageReceiver is a Player, then upon death, they will become a different MessageReceiver object. + // Thus, we use a supplier to supply the player from the server, if required. + Supplier<Optional<CommandSource>> messageReceiverSupplier; + if (receiver instanceof Player) { + final UUID playerUuid = ((Player) receiver).getUniqueId(); + messageReceiverSupplier = () -> Optional.ofNullable(Sponge.getServer().getPlayer(playerUuid).orElse(null)).map(x -> (Player) x); + } else { + WeakReference<CommandSource> srcReference = new WeakReference<>(receiver); + messageReceiverSupplier = () -> Optional.ofNullable(srcReference.get()); + } + + ActivePagination pagination; + if (this.contents instanceof List) { // If it started out as a list, it's probably reasonable to copy it to another list + pagination = new ListPagination(messageReceiverSupplier, calculator, ImmutableList.copyOf(counts), title, this.header.orElse(null), + this.footer.orElse(null), this.paginationSpacer); + } else { + pagination = new IterablePagination(messageReceiverSupplier, calculator, counts, title, this.header.orElse(null), + this.footer.orElse(null), this.paginationSpacer); + } + + GDPaginationHolder.getInstance().getPaginationState(receiver, true).put(pagination); + try { + pagination.specificPage(page); + } catch (CommandException e) { + TextAdapter.sendComponent(receiver, e.getText()); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/IterablePagination.java b/sponge/src/main/java/com/griefdefender/internal/pagination/IterablePagination.java new file mode 100644 index 0000000..0de9ebb --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/IterablePagination.java @@ -0,0 +1,127 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import com.google.common.base.Function; +import com.google.common.collect.Iterators; +import com.google.common.collect.Lists; +import com.google.common.collect.PeekingIterator; +import com.griefdefender.command.CommandException; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import org.spongepowered.api.command.CommandSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +/** + * Pagination occurring for an iterable -- we don't know its size. + */ +class IterablePagination extends ActivePagination { + + private final PeekingIterator<Map.Entry<Component, Integer>> countIterator; + private int lastPage; + + public IterablePagination(Supplier<Optional<CommandSource>> src, GDPaginationCalculator calc, Iterable<Map.Entry<Component, Integer>> counts, + @Nullable Component title, @Nullable Component header, @Nullable Component footer, Component padding) { + super(src, calc, title, header, footer, padding); + this.countIterator = Iterators.peekingIterator(counts.iterator()); + } + + @Override + protected Iterable<Component> getLines(int page) throws CommandException { + if (!this.countIterator.hasNext()) { + throw new CommandException(TextComponent.of("You're already at the end of the pagination list iterator.")); + } + + if (page < 1) { + throw new CommandException(TextComponent.of(String.format("Page %s does not exist!", page))); + } + + if (page <= this.lastPage) { + throw new CommandException(TextComponent.of("You cannot go to previous pages in an iterable pagination.")); + } else if (page > this.lastPage + 1) { + getLines(page - 1); + } + this.lastPage = page; + + if (getMaxContentLinesPerPage() <= 0) { + return Lists.newArrayList(Iterators.transform(this.countIterator, new Function<Map.Entry<Component, Integer>, Component>() { + + @Nullable + @Override + public Component apply(Map.Entry<Component, Integer> input) { + return input.getKey(); + } + })); + } + + List<Component> ret = new ArrayList<>(getMaxContentLinesPerPage()); + int addedLines = 0; + while (addedLines <= getMaxContentLinesPerPage()) { + if (!this.countIterator.hasNext()) { + // Pad the last page, but only if it isn't the first. + if (page > 1) { + padPage(ret, addedLines, false); + } + break; + } + if (addedLines + this.countIterator.peek().getValue() > getMaxContentLinesPerPage()) { + // Add the continuation marker, pad if required + padPage(ret, addedLines, true); + break; + } + Map.Entry<Component, Integer> ent = this.countIterator.next(); + ret.add(ent.getKey()); + addedLines += ent.getValue(); + } + return ret; + } + + @Override + protected boolean hasPrevious(int page) { + return false; + } + + @Override + protected boolean hasNext(int page) { + return page == getCurrentPage() && this.countIterator.hasNext(); + } + + @Override + protected int getTotalPages() { + return -1; + } + + @Override + public void previousPage() throws CommandException { + throw new CommandException(TextComponent.of("You cannot go to previous pages in an iterable pagination.")); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/ListPagination.java b/sponge/src/main/java/com/griefdefender/internal/pagination/ListPagination.java new file mode 100644 index 0000000..c75451f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/ListPagination.java @@ -0,0 +1,107 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import com.google.common.collect.ImmutableList; +import com.griefdefender.command.CommandException; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import org.spongepowered.api.command.CommandSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + +/** + * Pagination working with a list of values. + */ +class ListPagination extends ActivePagination { + private final List<List<Component>> pages; + + public ListPagination(Supplier<Optional<CommandSource>> src, GDPaginationCalculator calc, List<Map.Entry<Component, Integer>> lines, + @Nullable Component title, @Nullable Component header, @Nullable Component footer, Component padding) { + super(src, calc, title, header, footer, padding); + List<List<Component>> pages = new ArrayList<>(); + List<Component> currentPage = new ArrayList<>(); + int currentPageLines = 0; + + for (Map.Entry<Component, Integer> ent : lines) { + final boolean finiteLinesPerPage = getMaxContentLinesPerPage() > 0; + final boolean willExceedPageLength = ent.getValue() + currentPageLines > getMaxContentLinesPerPage(); + final boolean currentPageNotEmpty = currentPageLines != 0; + final boolean spillToNextPage = finiteLinesPerPage && willExceedPageLength && currentPageNotEmpty; + if (spillToNextPage) { + padPage(currentPage, currentPageLines, true); + currentPageLines = 0; + pages.add(currentPage); + currentPage = new ArrayList<>(); + } + currentPageLines += ent.getValue(); + currentPage.add(ent.getKey()); + } + //last page is not yet committed + final boolean lastPageNotEmpty = currentPageLines > 0; + if (lastPageNotEmpty) { + if (!pages.isEmpty()) { + // Only pad if we have a previous page + padPage(currentPage, currentPageLines, false); + } + pages.add(currentPage); + } + this.pages = pages; + } + + @Override + protected Iterable<Component> getLines(int page) throws CommandException { + final int size = this.pages.size(); + if (size == 0) { + return ImmutableList.of(); + } else if (page < 1) { + throw new CommandException(TextComponent.of(String.format("Page %s does not exist!", page))); + } else if (page > size) { + throw new CommandException(TextComponent.of(String.format("Page %s is greater than the max of %s!", page, size))); + } + return this.pages.get(page - 1); + } + + @Override + protected boolean hasPrevious(int page) { + return page > 1; + } + + @Override + protected boolean hasNext(int page) { + return page < this.pages.size(); + } + + @Override + protected int getTotalPages() { + return this.pages.size(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/PaginationList.java b/sponge/src/main/java/com/griefdefender/internal/pagination/PaginationList.java new file mode 100644 index 0000000..b0624fa --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/PaginationList.java @@ -0,0 +1,274 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import static com.google.common.base.Preconditions.checkNotNull; + +import net.kyori.text.Component; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.text.channel.MessageReceiver; + +import java.util.List; +import java.util.Optional; + +import javax.annotation.Nullable; + +/** + * Represents an immutable iterable of {@link Text}s, which can be sent to + * a {@link MessageReceiver}. + * + * <p>An instance of this class may be obtained using {@link Builder}.</p> + */ +public interface PaginationList { + + /** + * Creates a new {@link Builder} to build a pagination list. + * + * @return The new builder + */ + public static GDPaginationBuilder builder() { + return new GDPaginationBuilder(); + } + + /** + * Gets the contents of this pagination list. + * + * @return The contents of this pagination list + */ + Iterable<Component> getContents(); + + /** + * Gets the title text to be used in the title bar of this pagination. + * + * @return The title text + */ + Optional<Component> getTitle(); + + /** + * Gets the header to be displayed for this output on all pages after the + * title bar but before the contents, if available. + * + * <p>Header and footer will use this Text's style and color for + * formatting.</p> + * + * @return The header to be displayed + */ + Optional<Component> getHeader(); + + /** + * Gets the footer to be displayed for this output on all pages after the + * contents and page navigation bar, if available. + * + * @return The footer + */ + Optional<Component> getFooter(); + + /** + * Gets the padding character to be used when centering headers and footers. + * + * @return The padding character + */ + Component getPadding(); + + /** + * Gets the maximum amount of lines that will be sent per page. + * + * <p>This defaults to the maximum amount of lines that can be displayed + * on a source's screen at one time if not specified.</p> + * + * @return The maximum amount of lines that will be sent per page + */ + int getLinesPerPage(); + + /** + * Sends the first page of the constructed pagination list + * to the specified message receiver. + * + * @param receiver The receiver to send the first page to + * @see PaginationList#sendTo(MessageReceiver, int) to send a specific page + */ + default void sendTo(CommandSource receiver) { + sendTo(receiver, 1); + } + + /** + * Send the specified page of the constructed pagination list + * to the specified message receiver. + * + * <p>A page that is out of bounds will result in a friendly + * error message being sent to the receiver.</p> + * + * <p>Pages start at an index of 1.</p> + * + * @param receiver The receiver to send the page to + * @param page The page to send, starting at an index of 1 + */ + void sendTo(CommandSource receiver, int page); + + /** + * Sends the first page of the constructed pagination list to + * all {@link MessageReceiver}s within an {@link Iterable}. + * + * @param receivers The message receivers to send the first page to + * @see PaginationList#sendTo(Iterable, int) to send a specific page + */ + default void sendTo(Iterable<CommandSource> receivers) { + sendTo(receivers, 1); + } + + /** + * Sends the specified page of the constructed pagination list + * all {@link MessageReceiver}s within an {@link Iterable}. + * + * @param receivers The message receivers to send the page to + * @param page The page to send + */ + default void sendTo(Iterable<CommandSource> receivers, int page) { + checkNotNull(receivers, "The iterable of receivers cannot be null!"); + for (CommandSource receiver : receivers) { + sendTo(receiver, page); + } + } + + /** + * Builds a paginated output for an iterable of {@link Text}s. + */ + interface Builder { + + /** + * Sets the contents of this output as an iterable. + * + * <p>If this {@link Iterable} is a {@link List}, bidirectional + * navigation is supported. Otherwise, only going to the next page will + * be supported.</p> + * + * @param contents The contents to output + * @return This builder + */ + Builder contents(Iterable<Component> contents); + + /** + * Sets the contents of this output to be the given array of contents. + * + * @param contents The contents to output + * @return This builder + */ + Builder contents(Component... contents); + + /** + * Sets the title text to be used in the title bar of this pagination. + * + * <p>This should be less than one line long.</p> + * + * @param title The title to use + * @return This builder + */ + Builder title(@Nullable Component title); + + /** + * Sets the header to be displayed for this output on all pages after + * the title bar but before the contents. + * + * <p>The header and footer will use this Text's style and color for + * formatting.</p> + * + * <p>If the header is not specified, or passed in as <code>null</code>, + * it will be omitted when displaying the list.</p> + * + * @param header The header to set + * @return This builder + */ + Builder header(@Nullable Component header); + + /** + * Sets the footer to be displayed for this output on all pages after + * the contents and page navigation bar. + * + * <p>If the footer is not specified, or passed in as <code>null</code>, + * it will be omitted when displaying the list.</p> + * + * @param footer The footer to set + * @return This builder + */ + Builder footer(@Nullable Component footer); + + /** + * Sets the padding character to be used when centering headers and + * footers. + * + * @param padding The padding to use + * @return This builder + */ + Builder padding(Component padding); + + /** + * Sets the maximum number of lines that can be displayed per page. + * + * <p>This defaults to the maximum amount of lines that can be displayed + * on a source's screen at one time if not specified.</p> + * + * @param linesPerPage The maximum number of lines to display per page + * @return This builder + */ + Builder linesPerPage(int linesPerPage); + + /** + * Creates a {@link PaginationList} from this pagination builder. + * + * @return The pagination list + * @throws IllegalStateException If no contents were specified + */ + PaginationList build(); + + /** + * Sends the constructed pagination list to the given receiver. + * + * @param receiver The receiver to send the list to + * @return The constructed pagination list + */ + default PaginationList sendTo(CommandSource receiver) { + final PaginationList list = build(); + list.sendTo(receiver); + return list; + } + + /** + * Sends the constructed pagination list to all + * {@link MessageReceiver}s within an {@link Iterable}. + * + * @param receivers The message receivers to send the list to + * @return The constructed pagination list + */ + default PaginationList sendTo(Iterable<CommandSource> receivers) { + checkNotNull(receivers, "The iterable of receivers cannot be null!"); + final PaginationList list = build(); + for (CommandSource r : receivers) { + list.sendTo(r); + } + return list; + } + + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterable.java b/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterable.java new file mode 100644 index 0000000..a736301 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterable.java @@ -0,0 +1,49 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import net.minecraft.util.text.ITextComponent; + +import java.util.Iterator; + +public class TextComponentIterable implements Iterable<ITextComponent> { + + private final ITextComponent component; + private final boolean includeSelf; + + public TextComponentIterable(ITextComponent component, boolean includeSelf) { + this.component = component; + this.includeSelf = includeSelf; + } + + @Override + public Iterator<ITextComponent> iterator() { + if (this.includeSelf) { + return new TextComponentIterator(this.component); + } + return new TextComponentIterator(this.component.getSiblings().iterator()); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterator.java b/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterator.java new file mode 100644 index 0000000..b3d8811 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/TextComponentIterator.java @@ -0,0 +1,101 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.pagination; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.UnmodifiableIterator; +import net.minecraft.util.text.ITextComponent; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.annotation.Nullable; + +public class TextComponentIterator extends UnmodifiableIterator<ITextComponent> { + + private ITextComponent component; + private Iterator<ITextComponent> children; + @Nullable private Iterator<ITextComponent> currentChildIterator; + + public TextComponentIterator(ITextComponent component) { + this.component = checkNotNull(component, "component"); + } + + public TextComponentIterator(Iterator<ITextComponent> children) { + this.children = checkNotNull(children, "children"); + if (this.children.hasNext()) { + this.setCurrentChildIterator(); + } + } + + @Override + public boolean hasNext() { + return this.component != null || (this.currentChildIterator != null && this.currentChildIterator.hasNext()); + } + + // In order for this method to work properly, 'currentChildIterator' must be ready to return an element + // (i.e its 'hasNext()' method returns true) when this method returns. If this condition can no longer be met, + // we're done iterating. + @Override + public ITextComponent next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + if (this.component != null) { + return this.init(); + } + + ITextComponent result = this.currentChildIterator.next(); + + if (!this.currentChildIterator.hasNext() && this.children.hasNext()) { + this.setCurrentChildIterator(); + } + + return result; + } + + private ITextComponent init() { + this.children = this.component.getSiblings().iterator(); + + ITextComponent result = this.component; + this.component = null; + + // An iterator of an empty TextComponentTranslation doesn't have children. Thus, calling 'this.currentChildIterator.next()' + // at the end of this method will lead to a NoSuchElementException. To fix this, we + // initialize currentChildIterator so that the following call to 'hasNext()' will properly return 'false' if necessary + if (this.children.hasNext()) { + this.setCurrentChildIterator(); + } + + return result; + } + + private void setCurrentChildIterator() { + this.currentChildIterator = new TextComponentIterable(this.children.next(), true).iterator(); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/pagination/font-sizes.json b/sponge/src/main/java/com/griefdefender/internal/pagination/font-sizes.json new file mode 100644 index 0000000..b6954cb --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/pagination/font-sizes.json @@ -0,0 +1,50 @@ +# To create/update this file: Use the following code (accurate as of MC 1.8), run on the client. The nonUnicode string constant is extracted from FontRenderer -- it's inlined though. +# This must be run while a game is active -- I've stuck this in a command +# ```java +# public void printCharSizes() { +# ConfigurationNode node = SimpleConfigurationNode.root(); +# String nonUnicode = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153" +# + "\u015e" +# + "\u015f\u0174" +# + "\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;" +# + "<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; +# List<Integer> nonUnicodeCodePoints = new ArrayList<Integer>(nonUnicode.length()); +# for (int i = 0; i < nonUnicode.length(); ++i) { +# nonUnicodeCodePoints.add(nonUnicode.codePointAt(i)); +# } +# node.getNode("non-unicode").setValue(nonUnicode); +# int[] charWidths = Minecraft.getMinecraft().getRenderManager().getFontRenderer().charWidth; +# final List<Integer> charWidthsList = new ArrayList<Integer>(charWidths.length); +# for (int i : charWidths) { +# charWidthsList.add(i); +# } +# charWidthsList.set(32, 4); // Space is handled weirdly +# node.getNode("char-widths").setValue(charWidthsList); +# InputStream var1 = null; +# +# final List<Byte> glyphSizezList = new ArrayList<Byte>(65536); +# try { +# var1 = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream(); +# int b; +# while ((b = var1.read()) != -1) { +# glyphSizezList.add((byte) b); +# } +# } catch (IOException var6) { +# throw new RuntimeException(var6); +# } finally { +# IOUtils.closeQuietly(var1); +# } +# node.getNode("glyph-widths").setValue(glyphSizezList); +# +# try { +# HoconConfigurationLoader.builder() +# .setRenderOptions(ConfigRenderOptions.concise()) +# .setFile(new File("font-sizes.conf")) +# .build().save(node); +# } catch (IOException e) { +# logger.error("Unable to write character size information", e); +# } +# } +# ``` + +{"char-widths":[6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,4,4,6,7,6,6,6,6,6,6,1,1,1,1,1,1,1,4,2,5,6,6,6,6,3,5,5,5,6,2,6,2,6,6,6,6,6,6,6,6,6,6,6,2,2,5,6,5,6,7,6,6,6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,4,6,6,3,6,6,6,6,6,5,6,6,2,6,5,3,6,6,6,6,6,6,6,4,6,6,6,6,6,6,5,2,5,7,6,6,6,6,6,6,6,6,6,6,6,6,4,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,6,3,6,6,6,6,6,6,6,7,6,6,6,2,6,6,8,9,9,6,6,6,8,8,6,8,8,8,8,8,6,6,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,6,9,9,9,5,9,9,8,7,7,8,7,8,8,8,7,8,8,7,9,9,6,7,7,7,7,7,9,6,7,8,7,6,6,9,7,6,7,1],"glyph-widths":[15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,68,38,22,23,23,23,68,53,36,23,23,52,22,52,22,22,38,22,22,22,22,22,22,22,22,52,52,38,22,21,22,22,22,22,22,22,22,22,22,22,38,23,22,22,22,22,22,22,23,22,22,23,22,23,22,22,23,22,70,22,19,22,23,36,22,22,22,22,22,21,22,22,38,21,22,38,23,22,22,22,22,22,22,21,22,22,23,22,22,22,53,68,36,23,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,68,23,23,22,23,68,22,37,7,38,22,22,15,7,22,36,23,38,38,53,38,22,52,36,36,38,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,38,38,38,38,6,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,23,22,22,22,22,22,38,38,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,38,22,22,22,22,22,23,23,22,22,22,22,22,22,22,22,22,22,6,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,7,6,22,22,38,38,22,22,38,38,38,38,22,38,23,22,6,6,22,22,38,22,38,22,38,22,21,6,38,22,22,6,6,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,6,6,22,22,22,22,22,22,22,22,22,22,23,21,23,21,23,21,22,22,22,22,22,22,22,22,23,23,22,23,22,23,23,22,23,22,22,22,22,22,22,37,6,6,22,22,22,22,22,23,23,6,6,22,22,22,22,22,22,23,21,23,22,22,21,38,22,22,38,22,23,6,22,22,22,22,22,22,23,23,23,22,22,22,21,21,23,21,23,23,23,22,22,23,23,22,22,22,22,22,22,22,22,22,21,22,51,36,21,68,23,23,23,23,23,7,23,23,23,22,22,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,22,22,22,22,22,22,22,22,23,23,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,22,22,23,21,22,22,22,22,22,23,22,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,37,23,22,21,6,6,6,6,6,22,23,23,22,22,22,22,6,22,22,6,23,23,23,23,22,22,23,23,22,22,22,22,22,22,23,23,22,22,23,22,22,23,22,23,23,22,22,22,22,22,22,22,38,53,38,22,22,54,23,23,23,23,23,23,22,22,23,23,23,22,22,23,22,22,22,22,22,22,22,21,21,21,22,21,22,23,22,22,22,23,22,23,23,23,22,22,22,22,22,22,22,22,22,23,38,22,22,22,23,22,22,23,23,23,23,23,23,23,39,22,23,22,21,23,38,38,37,38,21,23,21,21,21,36,22,52,52,52,37,37,22,22,37,37,23,23,37,37,68,37,37,37,68,37,37,37,36,36,37,37,21,21,21,21,22,52,37,70,22,23,6,22,21,36,21,21,21,21,21,21,21,21,21,20,21,22,22,21,21,53,53,37,36,22,22,22,52,36,36,36,36,22,22,22,22,22,22,22,22,7,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,7,7,22,22,7,22,7,22,22,22,6,22,22,7,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,15,22,22,22,22,22,22,7,22,22,22,22,22,7,7,7,7,7,7,7,22,22,22,22,22,22,22,22,6,22,22,22,22,21,22,6,21,37,37,22,6,0,0,52,22,22,22,52,0,0,0,0,0,51,21,22,53,22,22,22,0,22,0,23,23,21,22,22,22,23,22,22,22,22,38,22,23,22,22,22,22,23,22,0,22,23,23,23,22,23,23,38,23,22,22,22,53,22,22,22,23,22,22,38,22,22,53,38,22,22,22,22,22,22,22,22,23,22,22,23,22,23,23,37,22,22,22,23,22,22,23,23,23,23,23,23,23,22,22,22,22,22,38,22,21,22,21,7,23,22,38,22,22,22,22,7,22,22,22,23,23,22,22,22,38,22,21,21,22,22,22,22,23,22,22,22,22,22,22,23,22,22,22,38,38,22,7,23,23,22,22,23,23,22,22,22,22,7,22,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,22,23,22,23,7,23,22,22,22,22,22,22,22,22,22,23,22,23,22,22,22,22,22,22,22,22,22,22,22,23,22,23,22,23,22,23,7,23,22,22,22,22,22,22,22,22,22,22,22,38,38,21,7,23,22,22,22,22,38,23,23,23,22,23,23,23,23,23,23,23,23,23,23,22,22,23,23,22,22,23,22,23,23,23,23,23,23,23,23,23,23,22,22,23,22,6,22,22,7,13,30,23,23,22,22,22,22,22,22,23,23,22,22,23,23,22,22,22,22,23,23,23,23,7,7,23,23,23,23,23,23,22,22,22,22,23,23,23,38,23,38,23,23,7,7,23,23,22,22,22,22,22,22,22,22,38,23,23,22,22,23,23,22,22,23,23,22,22,23,23,53,22,22,22,22,23,23,22,22,22,22,22,22,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,23,22,23,23,22,22,22,22,22,22,22,21,22,22,22,22,22,22,23,23,23,23,23,23,7,7,23,23,22,22,23,23,22,22,22,22,7,7,7,7,7,7,23,22,22,23,22,22,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,23,22,23,23,22,22,22,22,23,23,22,22,23,22,22,22,22,23,22,23,22,23,22,22,22,23,22,23,22,23,22,22,22,23,21,22,22,23,0,0,35,35,36,37,36,21,21,0,23,22,23,23,22,23,21,22,23,23,22,21,23,23,22,22,22,23,23,23,21,22,23,22,39,23,23,23,22,23,23,22,22,54,23,23,22,23,23,0,52,22,0,0,0,0,0,0,22,22,22,22,22,22,22,22,6,22,22,22,23,23,7,23,6,22,22,22,22,22,22,22,22,22,22,22,23,6,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,68,22,22,53,22,22,37,22,0,0,0,0,0,0,0,0,22,23,22,22,22,36,38,22,22,36,22,22,38,6,23,36,37,22,6,22,22,38,22,22,22,23,22,0,0,0,0,0,23,23,23,70,22,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,0,0,28,27,45,21,23,60,52,21,62,40,23,76,60,44,60,74,58,90,90,90,90,52,0,0,121,23,0,37,23,36,22,53,22,68,22,38,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,22,22,6,6,22,22,22,7,22,22,22,22,38,22,26,22,22,22,22,22,22,22,22,22,22,22,23,22,22,90,90,90,90,90,90,90,90,90,0,36,37,38,23,22,22,23,23,23,23,21,52,52,23,22,24,22,38,22,22,36,38,23,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,23,22,22,22,22,23,23,23,23,23,22,22,22,22,22,22,22,22,22,22,6,30,6,22,22,22,6,6,6,6,6,6,23,22,23,22,22,22,22,22,22,22,22,37,22,22,22,22,22,22,22,22,22,22,22,22,6,22,22,22,22,23,23,37,37,61,60,90,74,58,74,76,15,31,90,90,90,90,76,90,38,23,58,90,31,90,90,90,90,22,23,36,37,38,23,22,23,22,23,23,23,23,23,22,37,22,26,59,52,52,52,52,22,22,22,22,22,22,30,29,30,0,15,15,22,28,30,30,39,39,30,60,70,30,30,30,37,30,63,43,29,46,29,29,30,30,13,13,61,39,30,29,30,30,23,22,22,22,22,22,22,22,22,22,22,22,22,22,59,59,22,22,22,22,22,22,22,22,22,22,126,22,0,0,60,30,30,22,22,22,22,22,22,22,22,22,22,22,23,23,22,22,22,22,22,6,6,6,38,38,22,22,22,22,22,22,23,22,22,23,22,22,36,38,22,23,22,22,22,23,23,23,23,23,22,19,22,6,22,21,37,37,37,37,37,54,22,22,38,37,21,44,54,38,38,22,54,21,7,6,20,19,22,22,44,44,44,22,22,37,37,37,37,22,22,22,22,22,23,22,6,22,22,22,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,38,37,37,36,21,22,22,21,51,22,6,21,21,21,21,22,38,37,37,21,38,20,22,38,22,22,21,22,37,22,21,21,21,38,37,37,21,21,22,22,38,22,22,22,22,22,22,22,22,22,36,36,38,23,36,36,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,90,93,47,31,31,15,15,15,15,15,15,15,15,15,15,31,31,31,31,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,63,15,63,15,15,15,15,15,47,15,15,15,15,15,15,15,15,15,15,15,15,0,0,90,127,95,10,95,74,91,90,90,90,74,74,58,95,95,95,95,90,0,0,30,90,90,90,90,0,0,0,15,15,15,15,15,15,15,15,15,15,75,75,120,106,75,90,76,75,45,75,75,61,60,75,106,-119,31,0,0,0,0,0,0,0,0,15,15,75,15,15,0,90,95,94,0,15,15,15,15,15,15,31,30,0,0,30,30,0,0,30,30,15,47,47,15,45,15,15,15,15,30,15,15,15,15,63,15,31,15,79,15,0,31,15,15,15,15,15,15,0,15,0,0,0,31,15,15,15,0,0,90,13,95,12,79,90,91,90,90,0,0,10,10,0,0,15,15,90,75,0,0,0,0,0,0,0,0,95,0,0,0,0,15,15,0,15,31,75,91,94,0,0,60,76,60,45,44,44,45,76,29,45,15,15,59,28,59,45,45,-119,44,61,30,0,0,0,0,0,0,90,90,95,0,15,15,15,14,15,15,0,0,0,0,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,0,15,15,0,15,15,0,15,15,0,0,90,0,79,9,95,74,74,0,0,0,0,92,92,0,0,92,92,94,0,0,0,74,0,0,0,0,0,0,0,15,15,15,15,0,15,0,0,0,0,0,0,0,43,75,75,42,74,45,76,75,43,43,90,90,15,15,46,90,0,0,0,0,0,0,0,0,0,0,0,90,90,95,0,30,15,92,76,75,45,46,45,30,0,30,30,15,0,15,15,73,27,60,93,42,45,42,27,59,60,21,72,39,42,27,75,59,89,75,58,0,43,57,46,62,76,76,57,0,42,43,0,75,60,75,44,42,0,0,90,21,95,10,95,91,91,90,90,94,0,94,94,95,0,95,95,91,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,45,92,94,0,0,43,75,75,59,60,43,60,43,57,41,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,95,95,0,27,29,26,26,26,27,42,90,0,0,22,43,0,0,74,44,27,75,75,59,28,75,59,75,59,43,75,57,59,59,59,26,59,26,91,42,0,59,28,42,26,27,27,75,0,27,27,0,42,59,59,43,26,0,0,90,21,92,76,92,75,91,90,74,0,0,10,13,0,0,12,13,90,0,0,0,0,0,0,0,0,93,93,0,0,0,0,59,59,0,29,43,90,93,93,0,0,43,75,75,43,92,60,75,75,74,74,27,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,45,0,43,11,43,26,27,12,0,0,0,45,45,27,0,27,27,13,27,0,0,0,27,26,0,27,0,11,59,0,0,0,15,27,0,0,0,27,29,72,0,0,0,58,59,75,60,27,29,42,27,12,28,28,14,0,0,0,0,95,94,90,95,75,0,0,0,10,10,14,0,15,15,15,90,0,0,15,0,0,0,0,0,0,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,27,29,45,29,29,30,45,27,30,42,44,29,27,46,15,75,13,15,28,47,0,0,0,0,0,0,95,95,95,0,44,44,25,27,28,13,12,43,0,60,60,60,0,59,59,43,74,26,72,28,59,28,28,59,44,43,27,57,27,27,44,44,57,57,57,43,0,60,60,28,29,28,28,57,44,27,44,0,60,58,61,60,28,0,0,0,92,95,90,74,95,95,95,95,0,74,74,75,0,43,28,43,58,0,0,0,0,0,0,0,92,75,0,28,59,0,0,0,0,0,0,15,47,76,45,0,0,90,75,44,107,75,93,28,92,29,28,0,0,0,0,0,0,0,0,91,-120,106,74,60,92,92,60,0,0,95,95,0,44,44,25,11,11,14,13,43,0,28,28,28,0,28,28,28,58,45,43,12,28,12,12,26,44,43,27,57,27,27,28,59,57,57,57,43,0,28,28,28,28,12,12,57,44,26,27,0,28,26,29,43,76,0,0,90,22,95,90,95,95,95,95,95,0,77,79,79,0,79,79,62,92,0,0,0,0,0,0,0,95,95,0,0,0,0,0,0,0,44,0,15,31,76,61,0,0,90,75,91,60,75,45,28,92,26,28,0,74,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,95,0,13,15,27,12,57,44,59,44,0,12,12,15,0,43,45,29,28,29,29,30,28,44,45,28,14,29,74,73,45,44,28,45,28,58,44,44,0,28,28,28,58,60,45,44,59,44,43,59,28,45,28,12,12,0,0,0,108,95,95,95,95,95,95,95,0,10,10,10,0,15,15,15,95,0,0,0,0,0,0,0,0,0,95,0,0,0,0,0,0,0,0,28,29,44,44,0,0,90,44,28,29,58,60,30,74,42,27,45,44,30,60,43,44,0,0,0,30,29,44,59,28,44,29,0,0,95,95,0,60,14,31,31,59,59,59,30,15,15,30,15,42,45,15,29,29,15,0,0,0,12,44,10,26,27,30,42,46,44,14,13,14,29,42,27,44,27,14,44,12,27,92,44,13,0,74,61,61,44,29,44,44,42,57,0,44,0,0,27,11,43,26,29,62,44,0,0,0,95,0,0,0,0,95,95,95,76,76,59,0,59,0,95,10,15,10,15,15,15,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,95,14,0,0,0,0,0,0,0,0,0,0,0,0,22,21,22,22,22,22,21,22,22,38,22,23,22,22,22,38,22,23,23,22,22,22,22,21,22,38,38,23,23,23,23,22,22,22,21,22,22,22,21,22,23,23,22,23,22,23,21,37,23,21,22,22,22,22,22,22,22,22,0,0,0,0,22,69,55,23,22,22,21,22,22,22,23,23,22,23,22,23,22,22,22,22,22,22,22,22,22,23,22,38,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,0,22,0,0,21,22,0,22,0,0,22,0,0,0,0,0,0,22,22,22,6,0,22,22,22,7,6,6,6,0,22,22,22,0,22,0,22,0,0,22,7,0,22,22,38,37,23,37,6,22,22,22,22,22,22,0,23,22,22,0,0,69,38,22,22,6,0,22,0,22,22,23,22,22,22,0,0,22,22,21,22,22,22,22,22,22,22,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,12,12,7,22,7,7,22,39,23,52,15,52,38,52,52,22,23,7,37,7,30,31,31,22,37,23,23,53,23,23,39,55,39,39,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,53,23,22,22,23,23,12,46,22,39,23,6,23,23,23,23,23,23,23,23,0,23,23,39,23,23,39,23,39,23,23,39,23,23,23,23,23,23,23,23,23,23,23,39,23,23,39,23,23,39,23,23,23,23,23,23,23,0,0,0,0,22,22,23,22,22,22,23,22,23,6,6,7,7,22,22,22,23,23,23,87,23,22,22,40,23,6,23,0,0,0,0,23,23,23,23,23,23,23,23,0,23,23,22,23,23,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,0,23,23,23,38,22,23,23,44,29,14,44,44,14,14,14,0,44,44,29,22,89,29,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,41,41,13,40,41,12,42,9,25,28,8,6,24,26,12,10,10,40,57,25,41,57,57,26,57,10,25,10,40,26,10,6,11,27,44,11,25,25,57,58,14,15,94,95,90,90,90,91,95,90,90,90,90,90,91,95,90,90,94,30,90,90,31,42,57,55,39,73,89,89,72,57,89,68,37,22,92,78,12,57,57,57,57,39,41,95,95,90,91,91,93,75,91,90,90,90,74,94,94,94,90,91,94,94,94,94,94,94,94,29,29,27,91,90,91,90,58,58,58,11,11,11,11,27,27,27,57,28,57,91,95,10,90,90,94,94,94,95,94,95,91,74,94,90,89,73,89,90,74,74,90,90,90,0,0,0,0,123,76,6,6,6,6,21,6,6,6,21,21,6,6,21,6,6,22,6,21,22,6,6,21,22,21,21,21,22,22,22,22,21,6,6,22,22,21,6,6,0,0,0,0,0,0,0,0,0,0,38,38,38,23,38,38,22,23,38,38,23,38,38,23,38,22,23,38,22,22,23,38,23,38,22,38,38,38,39,22,38,38,38,38,38,38,38,22,21,38,38,39,38,38,38,0,0,0,40,40,40,40,40,40,40,40,40,55,25,55,40,41,40,40,40,24,40,45,45,45,45,45,45,45,45,45,45,61,45,45,45,45,30,30,29,30,30,45,45,45,45,76,45,61,61,61,61,61,61,30,46,61,61,61,61,61,61,61,60,60,58,61,42,61,61,62,61,62,29,61,61,61,61,61,55,45,42,44,45,45,45,45,42,43,45,60,45,44,0,0,0,0,0,15,15,-66,-66,-66,-66,-101,-98,-101,-98,46,46,46,43,46,46,43,46,43,46,46,43,-69,44,46,46,46,46,46,43,46,46,46,46,46,46,46,46,45,46,46,46,46,46,46,46,46,46,47,46,46,46,46,47,46,46,46,-98,-98,46,46,46,-117,-117,75,46,59,60,0,0,0,0,0,91,76,77,91,76,77,91,91,76,59,59,60,59,60,60,91,91,60,59,44,106,91,76,91,91,75,91,29,30,45,45,44,30,45,45,45,31,45,45,15,45,30,31,31,15,44,30,45,45,46,45,45,45,61,31,46,46,46,74,45,45,46,91,60,61,44,61,59,61,29,45,45,89,44,29,45,75,29,29,29,29,74,0,0,0,0,0,0,41,45,75,74,74,59,44,30,74,44,44,74,44,58,45,78,60,30,30,60,29,60,60,47,45,14,30,45,30,44,44,29,60,29,30,60,45,60,60,30,58,43,59,44,60,58,60,44,74,44,44,74,60,91,74,77,42,45,45,59,45,59,59,44,60,43,43,60,43,60,43,43,61,0,31,60,60,30,0,0,60,43,43,60,43,44,60,0,44,0,31,60,60,30,0,0,74,44,44,74,61,44,74,77,42,45,45,42,28,28,42,44,28,28,28,28,28,45,45,45,45,45,45,28,28,29,45,45,58,45,45,75,45,75,45,45,29,0,30,61,45,29,0,0,89,91,74,42,90,41,44,42,60,44,44,44,43,26,44,44,74,44,44,74,44,74,74,42,59,29,29,59,29,59,59,44,29,0,30,62,29,29,0,0,27,14,14,27,31,28,27,0,15,0,15,30,31,15,0,0,44,14,44,44,44,14,44,30,75,45,28,59,28,75,75,0,74,44,44,74,61,43,74,77,45,29,29,45,29,29,45,45,74,75,60,91,60,27,44,30,60,45,44,75,44,44,59,78,75,45,44,58,44,60,59,78,43,29,28,43,28,45,43,46,57,44,60,74,60,59,74,74,45,0,45,74,60,45,0,0,75,44,60,75,60,59,74,59,59,29,29,59,29,44,59,62,14,15,15,30,15,13,30,30,75,45,45,75,45,44,75,61,75,45,45,58,44,44,58,61,75,45,11,7,11,75,75,44,60,59,43,43,44,41,43,43,60,60,60,60,60,60,60,60,60,45,44,0,0,0,0,75,60,103,90,90,90,56,89,120,75,59,76,75,75,59,59,59,60,75,75,60,60,60,75,60,75,44,59,75,45,0,0,0,15,15,29,14,44,45,42,30,61,45,60,43,61,44,60,45,52,37,52,52,37,28,22,30,30,22,0,0,0,0,0,0,22,23,23,23,23,38,23,22,22,22,22,23,22,22,22,23,22,22,23,22,22,22,22,22,22,23,22,22,22,23,22,23,22,23,22,22,22,23,23,22,22,23,23,22,23,23,22,22,23,23,22,22,23,23,23,23,23,23,22,23,23,22,22,22,23,23,22,23,23,23,22,23,23,23,23,22,23,22,22,23,22,22,22,23,22,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,23,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,23,23,23,23,22,23,22,21,37,37,23,23,37,37,23,37,36,21,21,21,23,23,22,22,23,23,23,23,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,22,37,51,20,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,23,23,23,23,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,37,37,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,22,21,22,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,36,22,36,22,36,36,36,44,45,44,44,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,21,21,21,23,23,23,23,23,23,21,21,21,21,21,21,23,23,36,37,22,23,23,23,23,23,23,23,23,7,23,21,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,44,23,23,23,23,23,23,23,23,23,23,23,21,22,21,44,44,44,44,44,44,44,23,21,21,21,21,23,23,23,23,31,31,31,31,31,31,31,23,31,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,23,21,21,21,21,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,22,22,22,23,23,22,22,22,22,23,23,22,22,22,22,23,23,23,6,6,23,22,22,22,22,22,22,22,22,22,22,6,22,22,22,6,6,6,6,21,23,23,22,22,22,22,23,23,22,22,6,22,23,23,22,22,6,22,21,22,22,6,6,6,6,23,23,22,22,6,22,22,22,22,22,22,22,6,6,6,44,44,44,44,44,75,75,46,46,46,29,60,60,21,21,21,21,60,60,6,6,6,23,6,6,6,6,6,6,76,59,7,7,7,7,45,28,12,12,12,46,6,6,60,60,60,60,6,6,6,6,60,60,60,60,44,44,44,44,44,44,44,44,44,44,44,44,21,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,23,36,44,31,31,31,31,31,31,31,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,47,13,0,0,0,6,6,5,6,5,6,4,4,4,6,6,4,6,54,20,6,6,6,21,6,6,6,6,6,6,4,6,6,6,36,6,38,6,51,36,6,6,6,4,6,6,6,20,21,51,36,6,6,4,5,21,21,4,6,6,6,6,36,22,6,6,6,6,6,12,6,12,6,6,6,6,51,6,21,6,36,19,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,30,75,30,45,30,30,30,59,30,28,30,30,0,30,76,46,46,93,92,90,0,0,0,0,0,0,0,0,0,0,0,29,29,91,59,59,59,60,44,28,29,41,45,28,60,43,30,30,60,93,92,62,89,60,0,0,0,0,0,0,0,0,0,44,44,75,59,91,61,30,44,92,45,74,60,45,60,43,60,44,74,93,92,0,0,0,0,0,0,0,0,0,0,0,0,30,30,36,44,29,30,13,30,29,30,45,30,30,0,30,30,29,0,93,92,0,0,0,0,0,0,0,0,0,0,0,0,75,90,75,44,74,43,75,46,61,90,91,59,46,45,45,75,75,77,59,90,76,75,76,59,59,61,89,61,90,76,60,60,61,60,44,44,29,91,62,91,75,59,75,76,78,76,77,75,76,91,75,92,15,15,94,92,91,92,92,90,90,90,11,15,15,10,10,10,14,14,90,95,94,90,75,90,92,92,90,94,93,43,15,90,56,24,20,30,15,74,15,90,38,75,0,0,74,59,44,44,77,75,59,46,60,44,0,0,0,0,0,0,20,21,34,7,21,21,36,6,36,21,0,0,0,0,0,0,29,120,74,74,75,14,120,72,60,60,120,15,15,15,15,0,45,45,45,45,45,44,45,45,44,45,0,0,0,0,0,0,60,28,44,60,60,60,60,60,28,61,60,44,60,12,78,78,76,78,75,43,40,24,40,24,56,28,44,44,40,40,44,45,45,40,56,123,60,77,60,28,60,60,24,24,60,28,28,77,75,56,24,24,40,56,40,62,78,40,29,44,24,46,40,75,61,56,24,30,61,62,60,60,77,76,24,45,62,62,78,77,60,44,28,24,46,40,45,60,0,0,0,0,0,0,0,0,60,105,75,44,44,90,14,60,61,45,28,24,40,56,40,40,40,40,61,29,30,46,45,60,56,43,62,60,78,76,45,44,44,77,28,45,62,62,72,24,61,10,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,56,9,25,8,27,25,24,24,39,42,24,25,25,24,56,10,8,25,24,24,23,57,40,8,26,38,41,25,0,0,0,75,92,75,94,95,78,79,58,42,95,94,94,0,0,0,0,95,95,90,93,95,95,95,93,95,92,90,74,0,0,0,0,25,0,0,0,20,25,74,75,59,74,44,74,74,58,59,58,21,21,21,6,6,22,6,6,22,37,6,37,6,37,6,7,21,6,22,85,37,37,37,37,37,37,37,37,37,6,0,0,23,21,21,22,21,0,0,0,0,0,0,0,0,0,0,0,75,58,59,90,59,59,58,58,90,75,93,73,74,42,42,90,59,74,59,74,60,60,59,59,75,59,57,59,43,59,75,59,59,73,59,74,73,59,59,74,59,59,0,0,0,0,0,0,95,95,95,95,95,10,10,10,95,95,10,95,95,95,95,95,95,89,58,89,59,59,73,59,95,95,0,0,0,0,0,0,75,76,90,106,75,59,90,44,30,74,0,0,0,0,45,45,76,89,59,59,73,73,89,76,74,89,45,45,30,30,45,45,74,73,59,59,72,74,73,76,74,73,45,45,30,30,45,45,23,60,38,7,60,6,23,43,23,23,23,60,60,60,29,30,29,21,29,29,108,29,29,94,95,10,95,10,0,0,7,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,74,90,91,95,45,14,44,15,75,61,60,14,46,15,58,15,10,15,30,30,29,29,27,60,28,44,15,15,27,27,28,43,15,30,43,60,45,60,43,44,14,28,44,29,43,14,60,15,27,43,14,90,95,58,58,95,95,75,79,28,31,26,10,31,15,58,63,95,30,15,30,60,60,43,15,0,0,0,0,105,31,46,10,72,75,58,14,44,30,15,15,105,91,90,45,89,-49,89,89,89,107,58,75,36,14,53,90,90,90,90,90,74,91,90,90,21,20,5,5,20,20,20,5,35,0,0,0,90,75,94,58,73,91,61,92,57,58,45,29,92,91,61,44,93,62,60,43,106,45,77,77,46,75,61,90,75,91,45,60,79,94,92,91,90,90,10,95,90,90,95,0,0,0,31,31,75,92,91,92,91,30,91,75,60,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,57,75,60,45,108,58,60,77,44,107,75,58,59,76,59,60,60,59,74,75,59,75,60,61,44,90,76,59,60,74,74,59,74,61,59,95,95,95,10,10,13,95,95,90,90,90,90,74,90,42,42,10,10,93,90,0,0,0,-120,105,106,91,60,89,105,75,75,75,75,59,106,90,106,0,0,0,59,59,44,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,6,22,22,22,6,22,22,6,22,6,22,6,6,6,6,22,7,6,6,7,22,22,6,6,6,52,52,52,6,36,21,51,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,22,6,6,22,21,5,21,22,21,21,38,22,38,21,22,22,23,23,23,23,21,38,21,22,22,22,23,22,39,23,23,23,22,22,22,22,6,22,23,23,22,23,22,21,22,21,6,21,21,21,22,22,21,21,38,38,38,21,21,22,21,22,22,23,22,22,22,22,22,6,21,21,22,22,22,22,22,21,20,23,22,22,22,38,38,22,21,22,22,23,22,6,22,23,22,23,22,21,22,22,22,22,23,21,23,22,7,6,23,21,7,23,23,23,23,7,22,6,22,22,22,45,21,21,23,23,23,23,23,21,23,22,38,23,23,22,22,23,22,22,22,22,22,23,23,23,23,22,60,39,22,21,7,22,22,21,21,38,38,21,38,20,21,21,37,21,21,22,21,36,54,23,23,23,23,38,22,21,37,21,22,23,23,38,22,38,38,23,23,22,38,22,22,22,22,22,22,22,6,6,6,22,6,6,7,22,22,23,6,22,6,6,6,7,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,6,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,21,22,22,21,22,21,22,22,22,22,22,6,23,22,22,38,38,22,22,22,22,22,22,22,38,22,38,22,38,22,38,22,23,22,23,22,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,21,23,21,23,21,23,22,22,22,22,22,22,22,22,22,22,22,23,22,23,21,22,23,22,23,22,23,21,23,21,23,21,21,22,22,23,22,22,22,22,22,22,22,22,21,23,22,22,37,21,21,22,22,21,22,22,22,22,22,22,22,22,23,39,23,38,22,21,22,21,22,6,22,6,22,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,38,38,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,22,23,22,23,22,23,22,6,7,22,21,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,6,6,22,22,22,22,22,22,0,0,6,6,6,6,6,6,0,0,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,53,53,21,21,21,21,22,22,6,6,6,6,6,6,6,6,22,22,22,22,22,22,0,0,6,6,6,6,6,6,0,0,22,22,22,22,22,22,22,22,0,7,0,7,0,7,0,7,23,23,23,23,23,23,23,23,7,7,7,7,7,7,23,23,22,22,22,22,22,22,53,53,22,22,22,22,23,23,0,0,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,23,23,23,23,23,23,23,23,7,7,7,7,7,7,7,7,22,22,22,22,22,0,22,22,22,22,6,6,22,52,53,52,22,22,22,22,22,0,22,22,6,6,6,6,22,21,21,22,21,37,22,22,0,0,22,22,38,38,6,6,0,21,21,22,22,22,22,22,22,22,22,22,23,23,7,7,6,22,22,52,0,0,23,23,23,0,23,23,6,6,7,7,23,52,52,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,37,37,22,22,23,7,53,7,52,52,52,52,22,22,22,22,23,23,21,37,52,38,23,52,15,15,15,15,15,15,15,15,7,7,37,7,7,37,7,7,21,70,19,23,37,22,22,22,22,22,23,37,22,70,19,22,22,22,21,6,38,21,23,52,22,23,22,7,22,6,37,15,21,21,51,6,21,51,51,15,15,15,15,15,15,0,0,0,0,0,15,15,15,15,15,15,38,68,0,0,38,38,38,38,38,38,38,38,38,69,52,38,38,36,38,38,38,38,38,38,38,38,38,38,38,69,52,0,21,21,21,22,21,0,0,0,0,0,0,0,0,0,0,0,22,22,22,7,23,23,23,23,23,23,22,22,22,6,23,23,39,7,22,7,7,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,6,6,22,22,22,22,7,7,7,22,23,14,30,14,14,7,30,30,46,22,22,46,22,7,61,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,6,22,23,23,22,22,6,23,7,7,23,22,6,38,23,7,22,7,23,7,7,23,22,23,23,7,23,22,22,7,7,7,7,22,22,23,23,22,22,21,22,23,22,22,21,22,22,22,7,23,22,23,22,23,22,44,30,61,61,23,44,60,22,22,22,6,60,60,6,22,6,22,23,29,7,21,29,0,0,0,22,22,22,22,22,6,22,22,22,22,22,22,22,53,22,23,23,23,7,7,7,23,23,23,23,22,22,22,23,53,22,23,23,23,23,23,7,23,23,23,23,53,22,23,23,22,22,59,22,22,6,6,7,29,0,0,0,0,0,0,0,22,38,22,38,7,38,22,22,22,22,23,23,23,23,7,38,7,38,23,23,22,38,22,38,38,23,23,23,23,7,7,22,21,38,21,38,23,21,7,7,22,22,22,22,22,22,70,36,22,22,70,36,22,7,22,22,7,22,7,22,22,6,7,23,22,38,22,38,7,38,22,22,22,22,6,23,7,7,38,38,23,38,23,38,22,22,7,23,7,23,23,23,23,23,23,23,23,23,23,38,29,7,7,7,7,44,28,45,29,7,7,45,23,37,22,22,22,23,23,23,22,22,22,22,22,22,22,23,23,22,22,23,23,22,22,38,37,37,23,23,23,22,23,22,22,22,22,52,22,37,22,23,23,22,22,38,23,7,38,23,7,39,39,22,22,22,52,22,22,22,22,22,22,22,23,23,52,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,7,7,37,22,38,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,22,22,23,23,37,37,22,22,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,38,38,38,38,22,22,23,23,22,22,38,52,38,38,38,38,38,38,38,22,23,23,22,22,23,23,38,22,38,21,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,52,7,7,7,27,59,22,59,59,22,25,25,44,42,22,42,22,76,23,6,22,38,38,38,38,53,54,20,54,20,71,20,71,20,22,23,23,23,23,23,23,7,23,22,7,22,20,54,20,54,70,36,22,22,23,23,7,23,22,20,54,7,23,7,23,22,23,7,23,23,22,22,23,38,23,23,23,23,23,23,23,7,7,23,23,23,23,23,23,23,23,23,23,38,23,23,23,23,23,38,38,23,23,23,23,23,38,38,23,38,23,23,23,23,23,23,38,38,38,23,23,23,22,22,37,23,23,22,38,23,23,22,22,54,22,23,22,22,54,23,22,30,30,29,60,38,6,29,29,29,45,59,30,30,44,12,12,12,30,45,45,59,59,59,59,29,30,23,21,29,29,14,29,38,35,38,21,69,21,38,35,38,21,69,21,54,20,54,52,20,54,20,68,7,22,22,30,30,30,30,30,23,0,119,7,7,7,7,38,38,44,44,44,44,44,44,44,44,44,44,44,39,39,29,30,6,68,6,6,6,14,14,14,30,30,30,6,15,15,15,14,14,30,30,29,76,30,29,29,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,30,29,29,30,30,29,30,26,27,27,27,27,27,27,27,28,29,29,29,29,30,30,29,29,27,30,30,25,25,25,25,25,30,22,22,22,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,38,38,38,38,38,22,7,23,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,30,30,30,30,30,30,30,30,30,30,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,73,59,59,60,59,59,60,59,59,12,11,13,13,12,13,13,13,13,13,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,15,30,30,30,30,30,30,30,30,30,30,15,15,15,15,15,15,15,15,15,15,30,7,7,68,52,7,7,68,52,6,6,68,52,71,71,55,55,4,4,4,4,71,71,55,55,4,4,4,4,71,71,55,55,55,55,55,55,4,4,4,4,4,4,4,4,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,6,6,68,52,7,53,71,55,55,4,5,5,71,55,55,4,5,5,71,55,55,4,5,5,7,7,7,7,7,7,7,7,7,71,4,4,71,7,7,7,3,68,71,68,3,52,71,52,7,52,7,52,15,15,15,15,15,15,15,15,15,13,11,9,7,5,3,1,-113,14,15,15,15,-17,7,-113,7,15,15,15,15,-113,15,15,23,23,23,23,23,23,23,23,23,23,37,37,23,23,37,37,7,7,22,22,22,22,22,22,37,37,22,22,22,22,22,22,22,22,37,37,22,22,23,23,23,23,22,23,23,23,23,23,23,23,23,23,23,23,20,71,7,7,7,7,20,71,71,20,23,23,22,22,22,22,21,22,22,22,22,23,23,22,22,7,22,22,22,22,7,7,7,7,22,22,22,22,22,37,37,22,23,7,23,15,23,23,23,38,22,7,7,7,21,23,23,23,23,23,23,38,23,45,44,44,29,30,23,23,23,37,23,37,7,37,29,14,29,38,38,22,38,23,7,44,61,7,23,29,29,29,29,29,29,29,29,29,23,7,7,7,23,39,22,38,38,38,23,22,22,23,7,22,23,23,23,23,7,23,23,23,23,22,22,22,23,23,23,23,22,22,23,23,23,23,22,22,23,23,38,23,23,23,38,23,7,20,22,7,7,22,22,22,23,23,15,14,14,14,14,14,14,14,14,15,14,14,12,45,59,59,59,59,59,59,12,12,12,12,29,29,29,29,29,29,22,22,15,30,29,59,14,44,44,29,14,29,14,14,0,0,29,22,44,44,60,75,44,14,6,13,6,6,38,44,61,29,47,44,6,5,6,6,29,22,22,7,6,6,23,0,0,0,45,45,30,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,13,14,14,0,14,14,14,30,0,0,14,31,28,14,28,14,14,29,14,44,14,28,28,14,14,14,14,59,29,29,14,14,14,14,14,15,14,14,0,14,14,14,14,14,14,14,15,14,14,14,14,14,29,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,30,14,14,14,14,0,46,0,62,62,62,62,0,0,0,29,0,120,105,75,74,74,14,14,0,0,30,59,59,14,29,14,30,54,37,38,38,38,38,39,22,23,6,53,36,22,22,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,44,14,44,14,14,14,14,14,14,14,30,30,30,14,14,91,14,30,30,14,14,14,14,15,0,15,30,14,44,30,44,44,14,44,14,14,14,14,14,0,22,44,21,60,60,38,38,6,30,30,36,0,45,0,0,0,76,6,59,22,22,45,45,30,59,59,29,29,30,30,30,121,6,29,14,31,14,31,38,21,37,37,22,22,54,20,70,19,44,44,45,45,14,14,14,13,14,14,13,13,30,13,30,14,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,14,14,31,14,46,30,31,30,89,89,59,59,31,14,31,14,13,14,23,23,30,30,30,30,30,30,30,30,30,31,31,30,30,46,46,45,28,28,28,30,46,30,29,45,45,46,46,46,46,30,30,30,29,29,60,60,37,37,45,45,45,45,45,45,44,44,30,30,30,14,14,14,14,21,30,30,21,21,30,36,30,36,31,14,21,21,31,14,21,21,46,29,21,21,46,29,21,21,30,76,30,76,30,30,30,30,30,30,30,30,76,76,30,14,14,31,14,14,76,30,76,76,30,76,23,23,59,59,38,105,37,22,22,37,37,37,37,37,37,70,19,54,20,54,20,38,21,61,61,61,61,37,37,119,104,46,45,45,46,46,61,61,46,46,46,46,22,22,46,46,46,46,45,45,45,45,59,59,59,59,59,44,59,59,59,59,59,59,61,59,61,61,61,61,29,45,27,27,27,27,27,45,29,29,31,14,59,44,44,6,6,6,6,6,6,6,36,36,91,91,44,59,44,30,45,46,44,30,30,30,30,59,29,29,59,6,91,91,57,57,57,57,57,57,30,21,21,21,23,6,6,6,6,6,29,29,61,61,61,58,59,59,59,30,30,44,59,59,13,74,74,74,74,61,74,74,75,74,74,59,74,74,74,74,74,44,22,37,60,37,21,21,5,21,22,74,21,21,21,21,21,74,74,38,21,21,21,59,75,75,44,14,8,29,29,29,22,22,53,60,75,75,75,75,59,59,37,37,37,37,6,6,75,75,59,59,46,59,59,44,44,45,45,60,77,29,44,44,44,44,59,59,59,59,59,59,44,44,23,23,44,44,5,5,5,5,44,5,5,5,5,5,44,29,30,5,45,59,59,43,60,74,74,74,74,74,74,74,74,75,58,74,74,75,58,5,5,75,58,5,5,21,21,22,22,74,74,74,74,74,74,74,74,75,75,75,75,59,59,45,93,29,59,59,59,59,59,59,59,59,44,59,76,59,59,59,59,59,59,59,59,59,59,30,30,44,44,22,22,22,22,22,22,22,22,44,44,22,22,22,22,30,30,59,59,59,59,22,22,22,22,29,29,59,59,59,59,59,37,59,59,44,43,45,60,45,45,59,59,59,59,59,60,60,21,21,21,21,6,75,74,44,51,44,44,74,74,44,74,75,36,36,44,44,44,44,30,29,6,6,44,44,44,44,30,6,30,30,30,30,60,60,60,60,60,60,60,60,44,30,30,37,37,45,45,44,44,30,30,6,6,6,6,21,21,21,14,14,22,22,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,14,14,30,30,30,30,30,30,0,0,0,29,44,44,61,61,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,6,6,6,6,21,6,6,7,6,6,6,7,37,6,61,6,38,6,22,6,6,7,6,6,6,6,6,7,6,6,60,6,6,6,22,6,6,6,45,28,13,7,6,53,6,22,0,21,38,6,6,6,21,6,23,7,6,6,6,6,37,22,44,22,38,22,21,6,6,6,6,6,6,22,38,6,6,21,60,22,6,6,21,22,22,6,45,45,30,6,22,53,22,22,0,22,21,23,22,22,7,7,22,22,23,23,22,22,22,22,22,0,7,6,23,6,21,21,23,6,22,6,38,53,21,0,0,7,7,22,21,22,21,7,6,6,6,22,22,6,6,22,22,7,7,36,36,22,22,7,6,6,21,6,21,7,6,6,6,7,22,22,22,22,22,6,6,7,7,6,6,23,23,7,7,6,6,6,21,22,21,6,22,21,37,6,22,7,7,7,7,6,6,6,6,7,5,37,37,7,7,22,22,6,6,76,75,22,21,22,22,37,37,6,22,7,22,7,23,6,6,22,22,7,7,6,6,22,7,7,14,23,23,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,21,21,6,37,22,37,22,6,6,21,6,43,22,60,22,37,60,38,21,44,7,7,59,21,5,60,60,6,6,22,22,37,5,59,60,21,22,7,44,21,21,21,22,59,0,0,0,0,0,0,0,0,0,0,37,6,6,6,6,6,6,21,21,22,22,22,6,6,21,6,6,6,52,7,21,6,22,6,60,36,21,6,6,37,22,51,6,51,37,37,6,6,21,52,7,6,6,23,21,6,23,22,21,21,36,6,37,6,0,0,0,0,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,46,29,9,10,9,43,43,61,61,43,8,44,44,44,45,30,28,44,29,13,27,30,0,0,0,0,0,0,0,0,0,44,29,29,44,29,26,45,0,60,60,60,60,60,44,28,0,30,30,30,30,30,30,30,0,15,15,15,14,15,14,14,0,60,60,60,43,60,44,60,0,76,60,60,76,60,60,76,0,11,14,14,11,14,11,11,0,42,27,27,25,27,41,25,0,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,22,6,37,37,37,37,37,37,21,21,5,5,5,21,21,21,29,30,30,31,18,44,44,44,37,22,22,7,22,23,21,21,23,23,37,37,37,37,37,37,22,22,22,5,22,22,22,7,22,52,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,13,44,59,14,73,27,14,43,-99,91,45,59,44,14,30,14,30,45,28,14,28,5,29,5,14,0,13,44,59,30,14,45,4,13,13,29,30,22,14,5,14,5,44,31,5,14,7,5,44,44,29,14,45,14,14,14,14,14,14,28,29,14,14,14,13,5,29,14,14,12,28,5,44,14,14,14,14,14,-83,6,14,14,14,29,60,21,14,14,30,29,14,14,14,6,7,6,14,13,14,14,14,29,46,14,14,46,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,15,119,90,25,14,87,15,14,30,30,14,14,45,14,5,30,29,28,29,29,60,29,45,14,108,90,14,30,14,44,29,14,14,14,14,59,14,14,14,30,14,14,14,13,44,29,29,14,46,44,14,29,14,14,30,13,44,28,29,57,14,29,13,14,30,14,14,14,14,14,14,59,46,27,30,14,14,14,30,14,46,14,45,30,14,29,14,14,14,28,14,30,30,14,30,14,14,14,14,14,13,44,14,14,14,44,14,14,60,14,14,14,14,45,14,30,14,14,14,29,30,29,30,29,14,30,30,14,30,45,46,60,14,45,14,14,14,46,29,30,14,29,14,14,14,14,46,29,30,14,14,14,29,44,14,14,14,30,14,14,14,14,46,14,14,14,14,14,45,14,30,14,14,30,14,30,14,14,14,14,30,14,14,13,14,14,29,30,14,14,29,45,29,30,29,30,46,14,14,14,14,14,14,14,30,29,14,46,14,14,14,14,14,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,44,44,44,45,44,29,44,44,58,29,13,74,89,29,29,57,12,74,43,29,44,60,29,29,29,29,60,59,59,44,60,29,44,29,119,27,44,29,29,30,29,0,0,0,45,29,15,45,14,15,29,30,44,14,30,29,14,30,15,15,44,59,45,30,45,14,59,28,14,45,45,44,29,29,-97,124,-97,124,107,109,107,109,31,31,29,28,31,31,28,28,28,31,31,28,-101,15,14,15,14,15,15,30,15,14,14,29,30,31,45,15,14,15,15,14,14,44,44,14,30,14,30,15,44,30,44,14,14,29,15,29,31,28,28,28,12,28,104,92,0,-120,106,41,41,43,42,41,41,41,41,41,42,42,42,44,41,44,44,74,45,29,29,29,29,29,60,30,30,29,30,29,60,29,29,29,55,39,23,22,23,0,0,0,0,0,0,0,0,41,87,59,14,75,43,43,43,30,44,41,45,106,30,43,77,30,119,90,73,89,60,29,61,43,90,104,90,60,46,44,76,30,45,43,29,0,0,0,0,0,0,0,0,0,0,0,0,22,23,6,37,6,59,22,6,75,6,60,6,5,77,5,59,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,27,27,44,59,74,74,75,44,74,74,59,59,59,75,59,28,28,28,44,44,59,91,59,59,91,44,59,59,76,74,59,59,59,75,75,74,59,74,74,59,59,59,74,59,74,74,90,74,74,59,75,75,43,29,29,44,58,58,59,59,59,59,59,59,59,75,75,75,75,59,74,74,75,90,58,75,75,59,59,59,44,44,43,59,59,59,59,59,44,59,28,45,60,45,30,30,59,74,74,59,59,74,74,59,43,60,45,45,43,60,60,58,58,59,59,59,74,75,75,75,60,59,59,74,45,59,59,59,44,59,59,59,75,58,58,59,29,29,75,59,59,30,44,44,74,59,59,59,59,59,75,44,45,45,45,60,60,44,60,44,44,74,74,74,74,59,59,59,75,75,75,59,59,59,59,59,60,44,44,60,60,59,59,59,59,59,59,45,59,76,76,75,45,45,60,42,59,60,59,59,75,59,59,75,59,91,91,75,29,60,60,60,58,75,59,59,44,44,59,44,44,59,43,43,43,59,44,44,59,43,59,59,44,59,59,59,44,60,60,60,45,59,59,45,43,43,59,58,58,29,59,59,45,59,59,45,59,59,59,45,45,30,60,60,60,29,74,74,74,44,44,44,59,59,60,75,44,44,59,60,60,44,43,60,60,43,43,75,75,59,59,75,29,75,75,29,74,44,44,29,60,60,59,59,59,59,60,44,44,60,59,59,60,44,44,44,59,45,45,44,74,74,74,74,59,59,59,74,74,44,59,59,59,60,59,59,60,74,74,74,44,44,60,60,60,44,75,59,59,59,45,45,44,74,59,59,59,59,59,60,60,60,44,44,59,59,30,60,44,44,74,44,44,29,45,45,74,74,45,30,44,75,75,59,59,59,44,60,60,60,44,44,75,75,30,30,45,74,74,44,75,59,59,60,59,59,60,59,59,59,43,60,75,75,59,59,60,74,74,44,45,45,44,59,59,59,60,75,75,59,44,44,75,43,60,60,29,29,44,73,73,59,74,73,73,74,60,74,74,45,75,60,60,44,59,74,74,74,74,74,60,59,59,59,29,44,44,29,60,60,60,90,90,44,75,75,44,59,59,59,44,59,59,59,59,29,29,29,59,29,44,44,59,44,74,74,44,59,59,59,59,44,44,90,59,75,59,44,44,44,60,60,60,44,60,60,74,59,59,59,59,59,74,74,59,59,59,59,59,57,74,59,74,74,60,60,60,60,75,75,44,75,44,44,59,44,74,74,73,59,59,59,44,60,59,59,59,59,59,30,30,59,60,60,45,44,44,74,74,59,59,60,59,59,44,44,74,74,59,59,59,75,44,44,59,73,59,59,75,45,45,45,45,44,90,90,59,74,74,74,44,59,59,44,60,60,30,44,60,60,44,44,89,89,59,74,74,60,74,74,59,60,60,60,59,43,60,60,60,44,44,59,60,59,59,44,45,45,74,45,60,60,60,75,75,75,75,75,74,60,60,60,59,59,59,75,60,60,60,60,60,60,59,73,73,45,74,29,29,59,60,60,74,74,89,89,59,29,29,60,73,73,75,45,45,60,59,59,74,30,91,91,59,59,59,59,60,59,59,74,44,44,44,59,59,59,59,45,29,29,59,75,75,74,90,90,74,59,59,60,44,44,90,59,59,59,60,60,59,59,59,75,75,59,75,75,59,59,59,60,90,90,59,75,75,74,57,74,75,74,74,75,59,59,59,74,74,74,60,59,59,59,59,90,90,59,59,29,59,59,59,90,106,59,59,59,44,59,59,59,29,29,59,60,59,59,59,90,90,74,90,59,59,74,75,75,59,59,58,75,43,59,59,59,59,59,59,59,59,60,91,91,60,60,60,60,59,44,44,59,60,59,59,59,74,74,59,60,59,59,59,59,44,44,44,59,59,59,59,59,44,44,44,74,44,59,59,59,44,44,44,59,45,60,60,74,60,60,74,59,59,75,60,60,74,74,74,74,44,44,44,45,14,60,60,29,59,59,59,44,60,60,75,29,29,60,60,60,75,60,60,45,45,59,59,44,74,74,59,28,74,74,60,59,59,59,44,74,74,60,75,75,75,60,60,59,59,59,75,75,75,75,60,45,45,59,74,45,45,29,59,59,75,43,43,76,60,60,60,44,44,29,74,74,60,59,59,59,45,59,60,60,60,74,44,44,75,75,75,60,90,90,59,58,75,60,60,60,74,74,74,59,44,74,74,44,75,75,59,74,43,60,59,74,74,60,91,91,45,60,60,74,43,60,60,45,29,29,75,59,59,59,60,59,59,59,59,59,90,90,75,59,59,60,74,74,74,60,60,45,74,74,59,45,44,44,60,75,59,59,59,59,59,59,59,75,59,59,59,59,59,60,74,74,74,59,59,74,59,59,59,58,44,44,74,59,59,60,45,29,29,59,59,75,75,74,75,75,44,74,74,75,59,59,59,45,59,59,59,59,59,59,29,30,30,74,90,90,75,28,45,59,59,59,74,59,59,59,60,60,73,30,30,30,29,75,75,44,44,58,58,74,59,75,75,75,59,60,60,74,59,59,59,44,74,74,59,60,60,14,74,43,60,60,43,60,44,45,45,74,45,59,59,59,59,59,59,58,59,75,75,75,74,59,59,74,74,74,74,73,73,59,44,44,0,0,0,119,73,90,90,74,90,73,44,60,60,90,105,75,75,74,90,74,74,74,60,59,74,44,74,59,89,59,74,59,59,60,90,89,89,90,59,29,59,29,44,75,58,75,90,89,74,74,44,60,44,45,74,74,59,89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,44,44,44,44,29,29,76,76,59,59,59,89,89,60,60,29,29,60,60,44,60,74,74,59,59,29,29,44,44,14,44,45,76,45,29,60,61,61,90,60,62,74,30,59,59,44,44,44,59,59,61,61,75,75,90,75,60,30,44,44,43,45,74,29,29,29,74,74,44,44,91,76,89,44,45,30,29,61,44,44,30,45,44,59,44,59,75,74,60,59,59,60,45,75,75,44,45,59,59,59,29,60,44,59,29,74,45,44,30,29,74,29,60,60,60,60,60,75,75,59,30,74,44,74,44,44,58,58,30,30,44,44,29,44,44,44,89,59,74,74,60,88,29,29,44,29,29,74,45,28,28,14,44,28,45,29,60,60,44,44,44,29,30,74,58,59,29,60,60,60,60,59,59,59,59,60,60,60,60,44,89,89,74,59,74,59,44,44,45,29,59,45,30,30,60,60,45,45,29,29,75,75,59,60,59,60,90,75,44,44,75,77,75,77,75,60,60,91,76,29,59,59,75,30,60,30,45,44,30,44,44,74,77,58,59,30,30,29,29,29,76,76,29,29,60,30,59,59,59,59,29,29,29,29,44,44,44,44,44,44,44,44,44,60,59,44,61,45,89,75,29,30,59,30,90,45,29,74,44,44,76,44,61,44,43,30,43,59,-120,93,25,74,75,58,59,90,60,29,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,22,22,22,22,37,37,6,21,22,22,29,29,23,23,43,43,22,22,22,22,6,22,22,22,23,23,6,23,43,7,0,0,45,60,45,44,45,44,22,22,6,23,45,45,31,22,13,22,14,6,0,0,0,0,0,0,0,0,22,22,22,70,7,23,22,37,30,30,6,6,7,23,7,7,6,6,23,23,23,23,43,60,22,21,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,37,37,37,37,37,19,19,19,19,19,53,53,53,53,53,54,54,54,54,54,19,54,21,21,21,21,51,51,51,22,22,21,37,22,21,22,22,44,45,22,38,21,21,7,7,21,22,45,45,45,45,45,45,44,44,44,45,44,44,22,22,6,6,22,22,6,6,22,55,6,38,7,7,7,7,45,45,6,6,7,7,7,7,23,23,60,60,22,22,6,22,7,6,22,23,22,21,6,22,6,22,21,38,21,21,22,22,22,22,22,45,44,29,28,59,28,27,6,6,22,22,21,22,22,22,22,38,23,22,21,21,6,6,22,52,37,68,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,22,38,15,15,15,90,15,15,15,91,15,15,15,15,92,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,95,95,15,90,95,20,20,29,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,58,58,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,59,60,61,60,60,0,0,0,0,0,0,0,0,94,94,8,9,8,8,8,8,8,9,8,8,8,8,8,8,8,8,24,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,24,8,8,8,8,8,8,9,8,8,8,8,8,8,8,8,8,8,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,92,0,0,0,0,0,0,0,0,0,-120,105,75,106,75,73,106,76,90,90,90,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,45,59,76,59,60,60,60,60,61,44,45,59,45,44,60,59,45,75,60,60,45,59,44,76,75,75,44,44,60,45,59,29,60,45,60,45,45,90,74,59,58,58,90,58,58,60,-120,58,58,29,58,59,45,75,59,44,76,29,29,45,45,60,29,43,44,60,61,45,29,59,10,10,10,94,10,10,10,94,94,94,94,94,94,0,0,0,0,0,0,0,0,0,0,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,27,74,61,60,60,59,46,60,46,44,44,44,47,29,45,31,31,31,60,62,44,60,46,46,46,28,45,14,44,30,30,45,45,30,76,61,45,30,45,45,93,95,95,95,94,95,10,10,94,93,95,10,93,95,0,0,0,0,0,0,0,0,0,59,43,45,93,45,60,30,44,30,77,44,29,90,95,0,0,75,43,46,46,29,57,76,77,45,45,0,0,43,-120,105,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,23,7,0,0,0,0,0,53,22,23,6,45,45,45,45,77,13,45,45,23,23,23,23,23,23,23,23,23,22,22,22,36,38,0,22,36,22,22,38,0,23,0,37,22,0,22,22,0,22,22,22,23,22,36,23,22,22,22,38,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,22,23,22,23,22,23,22,23,22,23,6,7,6,7,6,7,6,7,6,7,6,7,6,7,6,7,22,23,22,23,5,7,37,39,37,23,5,7,22,23,6,7,23,23,23,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,23,6,7,22,23,22,23,22,23,7,22,23,22,23,22,23,22,23,5,7,5,7,21,23,22,23,23,23,23,23,7,7,23,23,21,23,6,22,23,6,22,23,5,7,6,6,23,22,22,6,22,22,23,23,23,22,22,6,23,22,22,6,23,22,22,22,23,22,23,22,22,23,23,23,23,45,23,45,23,23,7,45,22,23,23,22,23,6,23,7,7,7,20,22,22,22,20,22,22,6,22,22,6,6,21,59,59,23,23,23,21,22,22,22,22,22,23,22,22,22,22,22,23,22,22,22,21,23,23,22,22,22,23,23,23,22,22,23,22,22,22,22,22,21,23,7,23,7,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,39,7,39,7,7,21,22,22,22,7,23,23,23,23,23,23,23,23,23,23,23,23,23,6,6,6,6,7,5,5,5,6,7,6,6,6,6,7,6,6,7,6,7,6,7,13,13,13,7,13,13,11,13,13,13,11,23,7,6,5,6,5,6,6,6,4,6,4,6,6,6,6,5,5,5,5,6,6,6,6,6,6,5,5,5,6,7,7,7,6,6,6,6,6,7,7,7,7,7,7,7,7,7,15,15,15,15,7,7,7,7,7,7,7,7,7,7,23,23,22,22,22,22,30,30,30,30,22,22,23,23,22,22,23,23,23,23,23,23,7,23,7,7,7,7,23,23,23,23,23,23,31,31,31,31,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,13,13,13,13,13,13,7,15,15,15,15,15,15,7,7,55,55,30,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,31,13,13,13,13,29,13,31,13,31,31,15,15,15,15,14,14,31,14,15,15,31,15,14,15,31,14,31,14,15,15,14,31,14,14,15,31,47,14,15,47,15,15,31,13,31,31,63,47,47,12,31,31,13,47,12,13,13,15,13,13,13,13,0,0,13,14,14,12,31,31,12,15,31,31,63,14,15,15,15,15,15,31,31,15,15,15,15,15,15,15,31,31,31,31,31,31,31,15,13,12,15,15,12,15,12,31,31,47,15,15,15,31,15,15,15,14,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,30,44,46,29,62,30,15,30,30,30,15,31,46,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-51,-84,-83,-51,-51,-35,-115,45,45,-51,0,0,0,0,0,0,23,6,23,6,23,6,7,0,0,0,0,0,0,0,0,0,120,-120,-120,17,2,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,-100,-99,30,30,46,30,15,15,46,30,15,104,104,120,0,104,120,104,-120,89,120,120,121,104,121,104,89,105,104,89,105,121,104,105,0,105,89,105,105,0,0,0,0,37,7,38,53,37,0,37,7,21,7,37,7,21,7,37,7,37,23,7,36,23,22,23,53,23,22,23,5,7,68,39,22,23,5,7,38,39,22,23,5,7,22,23,5,7,22,23,6,7,22,23,5,7,22,23,5,7,22,23,22,23,22,23,22,23,23,23,6,7,23,23,6,7,23,23,6,7,23,23,6,7,22,23,6,7,22,23,6,7,22,23,6,7,22,23,6,7,22,23,5,7,22,23,5,7,22,23,6,7,22,23,5,7,38,39,6,7,22,23,5,7,37,39,6,7,22,23,22,23,22,23,5,7,6,7,22,23,38,23,38,23,0,0,15,0,87,90,44,59,29,30,104,-100,54,59,61,52,46,52,29,60,91,76,60,61,76,59,59,60,59,120,120,44,45,61,75,45,46,60,44,60,60,76,46,45,74,77,61,60,29,29,45,77,46,61,60,44,45,45,30,44,44,60,-116,29,55,89,15,104,76,75,74,75,74,75,75,76,121,73,59,104,61,76,75,75,75,91,91,58,76,59,45,59,76,75,-116,-120,55,45,60,60,19,54,37,20,52,22,22,22,22,22,22,22,22,37,22,37,7,6,22,7,7,22,7,22,7,22,7,23,7,6,22,22,7,7,7,54,7,7,22,7,22,7,22,22,7,7,7,37,7,6,6,7,7,22,22,38,7,39,22,22,23,36,36,15,22,21,23,22,39,39,22,21,37,22,6,6,6,6,7,6,21,22,6,23,21,7,21,21,7,21,22,22,22,6,0,0,0,36,36,36,36,36,21,0,0,36,21,21,22,6,21,0,0,21,21,37,22,37,21,0,0,22,36,68,0,0,0,59,47,45,60,-120,61,46,0,51,22,38,22,38,22,22,0,0,0,0,0,0,0,0,0,0,15,15,15,15,22,15,15],"non-unicode":"ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000"} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/GDActor.java b/sponge/src/main/java/com/griefdefender/internal/provider/GDActor.java new file mode 100644 index 0000000..ccefac6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/GDActor.java @@ -0,0 +1,231 @@ +/* + * This file is part of GriefPrevention, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (C) sk89q <http://www.sk89q.com> + * Copyright (C) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider; + +import com.griefdefender.GriefDefenderPlugin; +import com.sk89q.util.StringUtil; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldVector; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; +import com.sk89q.worldedit.extent.inventory.BlockBag; +import com.sk89q.worldedit.internal.LocalWorldAdapter; +import com.sk89q.worldedit.internal.cui.CUIEvent; +import com.sk89q.worldedit.session.SessionKey; +import com.sk89q.worldedit.util.Location; +import io.netty.buffer.Unpooled; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketBuffer; +import net.minecraft.network.play.server.SPacketCustomPayload; +import net.minecraft.util.EnumHand; +import net.minecraft.util.text.TextComponentString; +import net.minecraft.util.text.TextFormatting; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.World; + +import java.nio.charset.Charset; +import java.util.UUID; + +import javax.annotation.Nullable; + +public class GDActor extends AbstractPlayerActor { + + private final EntityPlayerMP player; + public static final Charset UTF_8_CHARSET = Charset.forName("UTF-8"); + public static final String CUI_PLUGIN_CHANNEL = "WECUI"; + + public GDActor(Player player) { + this.player = (EntityPlayerMP) player; + } + + @Override + public UUID getUniqueId() { + return this.player.getUniqueID(); + } + + @Override + public int getItemInHand() { + ItemStack is = this.player.getHeldItem(EnumHand.MAIN_HAND); + return is == null ? 0 : Item.getIdFromItem(is.getItem()); + } + + @Override + public String getName() { + return this.player.getName(); + } + + @Override + public BaseEntity getState() { + throw new UnsupportedOperationException("Cannot create a state from this object"); + } + + @Override + public Location getLocation() { + return new Location( + this.getWorld(), + new Vector(this.player.posX, this.player.posY, this.player.posZ), + this.player.rotationYaw, + this.player.rotationPitch); + } + + @SuppressWarnings("deprecation") + @Override + public WorldVector getPosition() { + return new WorldVector(LocalWorldAdapter.adapt(this.getWorld()), this.player.posX, this.player.posY, this.player.posZ); + } + + @Override + public com.sk89q.worldedit.world.World getWorld() { + return GriefDefenderPlugin.getInstance().worldEditProvider.getWorld((World) this.player.getEntityWorld()) ; + } + + @Override + public double getPitch() { + return this.player.rotationPitch; + } + + @Override + public double getYaw() { + return this.player.rotationYaw; + } + + @Override + public void giveItem(int type, int amt) { + this.player.inventory.addItemStackToInventory(new ItemStack(Item.getItemById(type), amt, 0)); + } + + @Override + public void dispatchCUIEvent(CUIEvent event) { + String[] params = event.getParameters(); + String send = event.getTypeId(); + if (params.length > 0) { + send = send + "|" + StringUtil.joinString(params, "|"); + } + + PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send.getBytes(UTF_8_CHARSET))); + SPacketCustomPayload packet = new SPacketCustomPayload(CUI_PLUGIN_CHANNEL, buffer); + this.player.connection.sendPacket(packet); + } + + @Override + public void printRaw(String msg) { + for (String part : msg.split("\n")) { + this.player.sendMessage(new TextComponentString(part)); + } + } + + @Override + public void printDebug(String msg) { + sendColorized(msg, TextFormatting.GRAY); + } + + @Override + public void print(String msg) { + sendColorized(msg, TextFormatting.LIGHT_PURPLE); + } + + @Override + public void printError(String msg) { + sendColorized(msg, TextFormatting.RED); + } + + private void sendColorized(String msg, TextFormatting formatting) { + for (String part : msg.split("\n")) { + TextComponentString component = new TextComponentString(part); + component.getStyle().setColor(formatting); + this.player.sendMessage(component); + } + } + + @Override + public void setPosition(Vector pos, float pitch, float yaw) { + this.player.connection.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), yaw, pitch); + } + + @Override + public String[] getGroups() { + return new String[]{}; + } + + @Override + public BlockBag getInventoryBlockBag() { + return null; + } + + @Override + public boolean hasPermission(String perm) { + return ((Player) this.player).hasPermission(perm); + } + + @Nullable + @Override + public <T> T getFacet(Class<? extends T> cls) { + return null; + } + + @Override + public SessionKey getSessionKey() { + return new SessionKeyImpl(this.player.getUniqueID(), this.player.getName()); + } + + private static class SessionKeyImpl implements SessionKey { + // If not static, this will leak a reference + + private final UUID uuid; + private final String name; + + private SessionKeyImpl(UUID uuid, String name) { + this.uuid = uuid; + this.name = name; + } + + @Override + public UUID getUniqueId() { + return this.uuid; + } + + @Nullable + @Override + public String getName() { + return this.name; + } + + @Override + public boolean isActive() { + return Sponge.getServer().getPlayer(this.uuid).isPresent(); + } + + @Override + public boolean isPersistent() { + return true; + } + + } + +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/WorldEditProvider.java b/sponge/src/main/java/com/griefdefender/internal/provider/WorldEditProvider.java new file mode 100644 index 0000000..76ae676 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/WorldEditProvider.java @@ -0,0 +1,359 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.claim.GDClaimSchematic; +import com.griefdefender.command.CommandHelper; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionColors; +import com.griefdefender.internal.provider.worldedit.cui.event.MultiSelectionClearEvent; +import com.griefdefender.internal.provider.worldedit.cui.event.MultiSelectionColorEvent; +import com.griefdefender.internal.provider.worldedit.cui.event.MultiSelectionCuboidEvent; +import com.griefdefender.internal.provider.worldedit.cui.event.MultiSelectionGridEvent; +import com.griefdefender.internal.provider.worldedit.cui.event.MultiSelectionPointEvent; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.storage.FileStorage; +import com.griefdefender.util.PlayerUtil; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.world.World; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.data.DataContainer; +import org.spongepowered.api.data.persistence.DataFormats; +import org.spongepowered.api.data.persistence.DataTranslators; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.schematic.Schematic; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.zip.GZIPInputStream; + +public class WorldEditProvider { + + private final WorldEdit worldEditService; + private Map<UUID, GDActor> worldEditPlayers = new HashMap<>(); + private final Map<UUID, Path> schematicWorldMap = new HashMap<>(); + + public WorldEditProvider() { + this.worldEditService = WorldEdit.getInstance(); + } + + public WorldEdit getWorldEditService() { + return this.worldEditService; + } + + public LocalSession getLocalSession(String playerName) { + return WorldEdit.getInstance().getSessionManager().findByName(playerName); + } + + public World getWorld(String playerName) { + final LocalSession session = getLocalSession(playerName); + if (session == null) { + return null; + } + + return session.getSelectionWorld(); + } + + public World getWorld(org.spongepowered.api.world.World spongeWorld) { + for (World world : this.worldEditService.getServer().getWorlds()) { + if (world.getName().equals(spongeWorld.getName())) { + return world; + } + } + + return null; + } + + public RegionSelector getRegionSelector(Player player) { + final LocalSession session = getLocalSession(player.getName()); + if (session == null) { + return null; + } + + World world = session.getSelectionWorld(); + if (world == null) { + world = this.getWorld(player.getWorld()); + } + return session.getRegionSelector(world); + } + + public Vector createVector(Vector3i point) { + return new Vector(point.getX(), point.getY(), point.getZ()); + } + + public GDActor getOrCreateActor(Player player) { + if (this.worldEditPlayers.containsKey(player.getUniqueId())) { + return this.worldEditPlayers.get(player.getUniqueId()); + } + + final GDActor actor = new GDActor(player); + this.worldEditPlayers.put(player.getUniqueId(), actor); + return actor; + } + + public void removePlayer(Player player) { + this.worldEditPlayers.remove(player.getUniqueId()); + } + + public void visualizeClaim(Claim claim, Player player, GDPlayerData playerData, boolean investigating) { + this.visualizeClaim(claim, claim.getLesserBoundaryCorner(), claim.getGreaterBoundaryCorner(), player, playerData, investigating); + } + + public void visualizeClaim(Claim claim, Vector3i pos1, Vector3i pos2, Player player, GDPlayerData playerData, boolean investigating) { + // revert any current visuals if investigating + if (investigating) { + this.revertVisuals(player, playerData, null); + } + final LocalSession session = this.getLocalSession(player.getName()); + if (session == null || !session.hasCUISupport()) { + return; + } + final Vector point1 = this.createVector(pos1); + final Vector point2 = this.createVector(pos2); + final CuboidRegionSelector regionSelector = new CuboidRegionSelector(session.getSelectionWorld(), point1, point2); + final GDActor actor = this.getOrCreateActor(player); + session.setRegionSelector(this.getWorld(player.getWorld()), regionSelector); + actor.dispatchCUIEvent(new MultiSelectionCuboidEvent(claim.getUniqueId())); + actor.dispatchCUIEvent(new MultiSelectionPointEvent(0, point1, regionSelector.getArea())); + if (playerData.claimResizing != null) { + actor.dispatchCUIEvent(new MultiSelectionPointEvent(1)); + } else { + actor.dispatchCUIEvent(new MultiSelectionPointEvent(1, point2, regionSelector.getArea())); + } + + if (investigating || playerData.lastShovelLocation == null) { + actor.dispatchCUIEvent(new MultiSelectionColorEvent(MultiSelectionColors.RED, MultiSelectionColors.getClaimColor(claim), "", "")); + } + actor.dispatchCUIEvent(new MultiSelectionGridEvent(10)); + } + + public void visualizeClaims(Set<Claim> claims, Player player, GDPlayerData playerData, boolean investigating) { + for (Claim claim : claims) { + if (((GDClaim) claim).children.size() > 0) { + visualizeClaims(claim.getChildren(true), player, playerData, investigating); + } + final LocalSession session = this.getLocalSession(player.getName()); + if (session == null || !session.hasCUISupport()) { + return; + } + final Vector point1 = this.createVector(claim.getLesserBoundaryCorner()); + final Vector point2 = this.createVector(claim.getGreaterBoundaryCorner()); + final CuboidRegionSelector regionSelector = new CuboidRegionSelector(session.getSelectionWorld(), point1, point2); + final GDActor actor = this.getOrCreateActor(player); + //session.setRegionSelector(this.getWorld(player.getWorld()), regionSelector); + actor.dispatchCUIEvent(new MultiSelectionCuboidEvent(claim.getUniqueId())); + actor.dispatchCUIEvent(new MultiSelectionPointEvent(0, point1, regionSelector.getArea())); + if (playerData.claimResizing != null) { + actor.dispatchCUIEvent(new MultiSelectionPointEvent(1)); + } else { + actor.dispatchCUIEvent(new MultiSelectionPointEvent(1, point2, regionSelector.getArea())); + } + if (investigating) { + actor.dispatchCUIEvent(new MultiSelectionColorEvent(MultiSelectionColors.RED, MultiSelectionColors.getClaimColor(claim), "", "")); + } + actor.dispatchCUIEvent(new MultiSelectionGridEvent(10)); + } + } + + public void revertVisuals(Player player, GDPlayerData playerData, UUID claimUniqueId) { + final LocalSession session = this.getLocalSession(player.getName()); + if (session == null || !session.hasCUISupport()) { + return; + } + final World world = this.getWorld(player.getWorld()); + final RegionSelector region = session.getRegionSelector(world); + final GDActor actor = this.getOrCreateActor(player); + region.clear(); + session.dispatchCUISelection(actor); + if (claimUniqueId != null) { + actor.dispatchCUIEvent(new MultiSelectionClearEvent(claimUniqueId)); + } else { + actor.dispatchCUIEvent(new MultiSelectionClearEvent()); + } + } + + public void stopVisualDrag(Player player) { + final GDActor actor = this.getOrCreateActor(player); + actor.dispatchCUIEvent(new MultiSelectionClearEvent(player.getUniqueId())); + } + + public void sendVisualDrag(Player player, GDPlayerData playerData, Vector3i pos) { + final LocalSession session = this.getLocalSession(player.getName()); + if (session == null || !session.hasCUISupport()) { + return; + } + + final Location<org.spongepowered.api.world.World> location = BlockUtil.getInstance().getTargetBlock(player, playerData, 60, true).orElse(null); + Vector point1 = null; + if (playerData.claimResizing != null) { + // get opposite corner + final GDClaim claim = playerData.claimResizing; + final int x = playerData.lastShovelLocation.getBlockX() == claim.lesserBoundaryCorner.getX() ? claim.greaterBoundaryCorner.getX() : claim.lesserBoundaryCorner.getX(); + final int y = playerData.lastShovelLocation.getBlockY() == claim.lesserBoundaryCorner.getY() ? claim.greaterBoundaryCorner.getY() : claim.lesserBoundaryCorner.getY(); + final int z = playerData.lastShovelLocation.getBlockZ() == claim.lesserBoundaryCorner.getZ() ? claim.greaterBoundaryCorner.getZ() : claim.lesserBoundaryCorner.getZ(); + point1 = new Vector(x, y, z); + } else { + point1 = this.createVector(pos); + } + Vector point2 = null; + if (location == null) { + point2 = new Vector(player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ()); + } else { + point2 = this.createVector(location.getBlockPosition()); + } + + final CuboidRegionSelector regionSelector = new CuboidRegionSelector(session.getSelectionWorld(), point1, point2); + final GDActor actor = this.getOrCreateActor(player); + actor.dispatchCUIEvent(new MultiSelectionCuboidEvent(player.getUniqueId())); + actor.dispatchCUIEvent(new MultiSelectionPointEvent(0, point1, regionSelector.getArea())); + actor.dispatchCUIEvent(new MultiSelectionPointEvent(1)); + } + + public boolean hasCUISupport(Player player) { + return hasCUISupport(player.getName()); + } + + public boolean hasCUISupport(String name) { + final LocalSession session = this.getLocalSession(name); + if (session == null || !session.hasCUISupport()) { + return false; + } + + return true; + } + + public void createClaim(Player player) { + RegionSelector regionSelector = null; + Region region = null; + try { + regionSelector = GriefDefenderPlugin.getInstance().worldEditProvider.getRegionSelector(player); + region = regionSelector.getRegion(); + } catch (IncompleteRegionException e) { + TextAdapter.sendComponent(player, TextComponent.of("Could not find a worldedit selection.", TextColor.RED)); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final int minY = playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? region.getMinimumPoint().getBlockY() : 0; + final int maxY = playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? region.getMaximumPoint().getBlockY() : 255; + final Vector3i lesser = new Vector3i(region.getMinimumPoint().getBlockX(), minY, region.getMinimumPoint().getBlockZ()); + final Vector3i greater = new Vector3i(region.getMaximumPoint().getBlockX(), maxY, region.getMaximumPoint().getBlockZ()); + GDCauseStackManager.getInstance().pushCause(player); + final ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) + .bounds(lesser, greater) + .cuboid(playerData.getClaimCreateMode() == CreateModeTypes.VOLUME) + .owner(player.getUniqueId()) + .sizeRestrictions(true) + .type(PlayerUtil.getInstance().getClaimTypeFromShovel(playerData.shovelMode)) + .world(player.getWorld().getUniqueId()) + .build(); + GDCauseStackManager.getInstance().popCause(); + if (result.successful()) { + final Claim claim = result.getClaim().get(); + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(player.getWorld().getUniqueId()); + claimManager.addClaim(claim, true); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_SUCCESS, ImmutableMap.of( + "type", ((GDClaim) claim).getFriendlyNameType(true))); + GriefDefenderPlugin.sendMessage(player, message); + } else { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(result.getClaim().get()); + CommandHelper.showClaims(player, claims, 0, true); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTimingIfSync(); + } + } + } + + public Map<UUID, Path> getSchematicWorldMap() { + return this.schematicWorldMap; + } + + public void loadSchematics(org.spongepowered.api.world.World world) { + GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + GriefDefenderPlugin.getInstance().executor.execute(() -> { + for (Claim claim : claimWorldManager.getWorldClaims()) { + Path path = this.schematicWorldMap.get(claim.getWorldUniqueId()).resolve(claim.getUniqueId().toString()); + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + e.printStackTrace(); + } + return; + } + + File files[] = path.toFile().listFiles(); + for (File file : files) { + DataContainer schematicData = null; + Schematic schematic = null; + try { + schematicData = DataFormats.NBT.readFrom(new GZIPInputStream(new FileInputStream(file))); + schematic = DataTranslators.SCHEMATIC.translate(schematicData); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + GDClaimSchematic claimSchematic = new GDClaimSchematic(claim, schematic, schematic.METADATA_NAME); + ((GDClaim) claim).schematics.put(file.getName(), claimSchematic); + } + } + }); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionColors.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionColors.java new file mode 100644 index 0000000..abe0168 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionColors.java @@ -0,0 +1,48 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui; + +import com.griefdefender.api.claim.Claim; + +public class MultiSelectionColors { + + public static final String RED = "#990000"; + public static final String GREEN = "#5AC25A"; + public static final String YELLOW = "#EAEA32"; + public static final String GRAY = "#D2D2D2"; + + public static String getClaimColor(Claim claim) { + if (claim.isSubdivision()) { + return GRAY; + } + if (claim.isAdminClaim()) { + return RED; + } + if (claim.isTown()) { + return GREEN; + } + return YELLOW; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionType.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionType.java new file mode 100644 index 0000000..51023b5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/MultiSelectionType.java @@ -0,0 +1,36 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui; + +public class MultiSelectionType { + + public static final String COLOR = "+col"; + public static final String GRID = "+grid"; + public static final String GRID_CULL = "cull"; + public static final String POINT = "+p"; + public static final String SELECTION = "+s"; + public static final String SELECTION_CLEAR = "+s|clear"; + public static final String SELECTION_CUBOID = "+s|cuboid"; +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionClearEvent.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionClearEvent.java new file mode 100644 index 0000000..ca7813b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionClearEvent.java @@ -0,0 +1,54 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui.event; + +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionType; +import com.sk89q.worldedit.internal.cui.CUIEvent; + +import java.util.UUID; + +public class MultiSelectionClearEvent implements CUIEvent { + + private final String[] parameters; + + public MultiSelectionClearEvent() { + this.parameters = new String[] {}; + } + + public MultiSelectionClearEvent(UUID uniqueId) { + this.parameters = new String[] { + uniqueId.toString()}; + } + @Override + public String[] getParameters() { + return this.parameters; + } + + @Override + public String getTypeId() { + return MultiSelectionType.SELECTION_CLEAR; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionColorEvent.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionColorEvent.java new file mode 100644 index 0000000..67c108e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionColorEvent.java @@ -0,0 +1,60 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui.event; + +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionType; +import com.sk89q.worldedit.internal.cui.CUIEvent; + +public class MultiSelectionColorEvent implements CUIEvent { + + private final String edgeColor; + private final String gridColor; + private final String p1Color; + private final String p2Color; + private final String[] parameters; + + public MultiSelectionColorEvent(String edgeColor, String gridColor, String p1Color, String p2Color) { + this.edgeColor = edgeColor; + this.gridColor = gridColor; + this.p1Color = p1Color; + this.p2Color = p2Color; + this.parameters = new String[] { + this.edgeColor, + this.gridColor, + this.p1Color, + this.p2Color}; + } + + @Override + public String getTypeId() { + return MultiSelectionType.COLOR; + } + + @Override + public String[] getParameters() { + return this.parameters; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionCuboidEvent.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionCuboidEvent.java new file mode 100644 index 0000000..8316ce6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionCuboidEvent.java @@ -0,0 +1,54 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui.event; + +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionType; +import com.sk89q.worldedit.internal.cui.CUIEvent; + +import java.util.UUID; + +public class MultiSelectionCuboidEvent implements CUIEvent { + + protected final UUID uniqueId; + private final String[] parameters; + + public MultiSelectionCuboidEvent(UUID uniqueId) { + this.uniqueId = uniqueId; + this.parameters = new String[] { + String.valueOf(this.uniqueId) + }; + } + + @Override + public String getTypeId() { + return MultiSelectionType.SELECTION_CUBOID; + } + + @Override + public String[] getParameters() { + return this.parameters; + } + +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionGridEvent.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionGridEvent.java new file mode 100644 index 0000000..65dabc0 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionGridEvent.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui.event; + +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionType; +import com.sk89q.worldedit.internal.cui.CUIEvent; + +public class MultiSelectionGridEvent implements CUIEvent { + + final String[] parameters; + + public MultiSelectionGridEvent(double spacing) { + this.parameters = new String[] { String.valueOf(spacing), + MultiSelectionType.GRID_CULL}; + } + + @Override + public String[] getParameters() { + return this.parameters; + } + + @Override + public String getTypeId() { + return MultiSelectionType.GRID; + } + +} diff --git a/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionPointEvent.java b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionPointEvent.java new file mode 100644 index 0000000..3bd30aa --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/provider/worldedit/cui/event/MultiSelectionPointEvent.java @@ -0,0 +1,65 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.provider.worldedit.cui.event; + +import com.griefdefender.internal.provider.worldedit.cui.MultiSelectionType; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.internal.cui.CUIEvent; + +public class MultiSelectionPointEvent implements CUIEvent { + + private final String TILDE = "~"; + private final String[] parameters; + + // Used to force WECUI to follow player EYE + public MultiSelectionPointEvent(int id) { + this.parameters = new String[] { + String.valueOf(id), + TILDE, + TILDE, + TILDE, + String.valueOf(0)}; + } + + public MultiSelectionPointEvent(int id, Vector pos, int area) { + this.parameters = new String[] { + String.valueOf(id), + String.valueOf(pos.getX()), + String.valueOf(pos.getY()), + String.valueOf(pos.getZ()), + String.valueOf(area)}; + } + + @Override + public String getTypeId() { + return MultiSelectionType.POINT; + } + + @Override + public String[] getParameters() { + return this.parameters; + } + +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/BlockTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/internal/registry/BlockTypeRegistryModule.java new file mode 100644 index 0000000..19614f7 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/BlockTypeRegistryModule.java @@ -0,0 +1,69 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import org.spongepowered.api.block.BlockType; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +// internal +public final class BlockTypeRegistryModule { + + protected final Map<String, GDBlockType> blockTypeMappings = new HashMap<>(); + private final org.spongepowered.common.registry.type.BlockTypeRegistryModule SPONGE_REGISTRY = org.spongepowered.common.registry.type.BlockTypeRegistryModule.getInstance(); + + public static BlockTypeRegistryModule getInstance() { + return Holder.INSTANCE; + } + + public Optional<GDBlockType> getById(String id) { + if (!checkNotNull(id).contains(":")) { + id = "minecraft:" + id; + } + return Optional.ofNullable(this.blockTypeMappings.get(id.toLowerCase(Locale.ENGLISH))); + } + + public Collection<GDBlockType> getAll() { + return ImmutableList.copyOf(this.blockTypeMappings.values()); + } + + public void registerDefaults() { + for (BlockType type : SPONGE_REGISTRY.getAll()) { + this.blockTypeMappings.put(type.getId(), new GDBlockType(type)); + } + } + + private static final class Holder { + + static final BlockTypeRegistryModule INSTANCE = new BlockTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/EntityTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/internal/registry/EntityTypeRegistryModule.java new file mode 100644 index 0000000..dcf7e87 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/EntityTypeRegistryModule.java @@ -0,0 +1,90 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableList; +import net.minecraft.entity.EnumCreatureType; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.common.entity.SpongeEntityType; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public final class EntityTypeRegistryModule { + + protected final Map<String, GDEntityType> entityTypeMappings = new HashMap<>(); + public final BiMap<String, EnumCreatureType> SPAWN_TYPES = HashBiMap.create(); + private final org.spongepowered.common.registry.type.entity.EntityTypeRegistryModule SPONGE_REGISTRY = org.spongepowered.common.registry.type.entity.EntityTypeRegistryModule.getInstance(); + + public static EntityTypeRegistryModule getInstance() { + return Holder.INSTANCE; + } + + public Optional<GDEntityType> getById(String id) { + if (!checkNotNull(id).contains(":")) { + id = "minecraft:" + id; + } + return Optional.ofNullable(this.entityTypeMappings.get(id.toLowerCase(Locale.ENGLISH))); + } + + public Collection<GDEntityType> getAll() { + return ImmutableList.copyOf(this.entityTypeMappings.values()); + } + + public String getFriendlyCreatureTypeName(Living entity) { + return SPAWN_TYPES.inverse().get(((SpongeEntityType) entity.getType()).getEnumCreatureType()); + } + + public String getFriendlyCreatureTypeName(EnumCreatureType type) { + return SPAWN_TYPES.inverse().get(type); + } + + public EnumCreatureType getCreatureTypeByName(String name) { + return SPAWN_TYPES.get(name); + } + + public void registerDefaults() { + for (EntityType type : SPONGE_REGISTRY.getAll()) { + this.entityTypeMappings.put(type.getId(), new GDEntityType(type)); + } + SPAWN_TYPES.put("animal", EnumCreatureType.CREATURE); + SPAWN_TYPES.put("ambient", EnumCreatureType.AMBIENT); + SPAWN_TYPES.put("aquatic", EnumCreatureType.WATER_CREATURE); + SPAWN_TYPES.put("monster", EnumCreatureType.MONSTER); + } + + private static final class Holder { + + static final EntityTypeRegistryModule INSTANCE = new EntityTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/GDBlockType.java b/sponge/src/main/java/com/griefdefender/internal/registry/GDBlockType.java new file mode 100644 index 0000000..45dc80f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/GDBlockType.java @@ -0,0 +1,50 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import com.griefdefender.api.CatalogType; +import org.spongepowered.api.block.BlockType; + +public class GDBlockType implements CatalogType { + + private BlockType type; + + public GDBlockType(BlockType type) { + this.type = type; + } + + @Override + public String getName() { + return this.type.getName(); + } + + public String getId() { + return this.type.getId(); + } + + public BlockType getType() { + return this.type; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/GDEntityType.java b/sponge/src/main/java/com/griefdefender/internal/registry/GDEntityType.java new file mode 100644 index 0000000..df6c08e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/GDEntityType.java @@ -0,0 +1,104 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import com.griefdefender.api.CatalogType; +import com.griefdefender.api.permission.Context; +import net.minecraft.entity.EnumCreatureType; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.common.entity.SpongeEntityType; + +import javax.annotation.Nullable; + +public class GDEntityType implements CatalogType { + + public final int entityTypeId; + public final String modId; + public final EntityType type; + public EnumCreatureType creatureType; + + public GDEntityType(EntityType type) { + this.type = type; + this.modId = ((SpongeEntityType) this.type).modId; + this.entityTypeId = ((SpongeEntityType) type).entityTypeId; + this.creatureType = ((SpongeEntityType) type).getEnumCreatureType(); + } + + @Override + public String getName() { + return this.type.getName(); + } + + public String getId() { + return this.type.getId(); + } + + public String getModId() { + return this.modId; + } + + @Nullable + public String getEnumCreatureTypeId() { + if (this.creatureType == null) { + return null; + } + switch (this.creatureType) { + case CREATURE: + return this.modId + ":animal"; + case WATER_CREATURE: + return this.modId + ":aquatic"; + default: + break; + } + return this.modId + ":" + this.creatureType.name().toLowerCase(); + } + + @Nullable + public Context getEnumCreatureTypeContext(boolean isSource) { + if (this.creatureType == null) { + return null; + } + + final String contextKey = isSource ? "source" : "target"; + switch (this.creatureType) { + case CREATURE: + return new Context(contextKey, "#" + this.modId + ":animal"); + case WATER_CREATURE: + return new Context(contextKey, "#" + this.modId + ":aquatic"); + default: + break; + } + return new Context(contextKey, "#" + this.modId + ":" + this.creatureType.name().toLowerCase()); + } + + @Nullable + public EnumCreatureType getEnumCreatureType() { + return this.creatureType; + } + + public void setEnumCreatureType(EnumCreatureType type) { + this.creatureType = type; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/GDItemType.java b/sponge/src/main/java/com/griefdefender/internal/registry/GDItemType.java new file mode 100644 index 0000000..a9493e3 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/GDItemType.java @@ -0,0 +1,50 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import com.griefdefender.api.CatalogType; +import org.spongepowered.api.item.ItemType; + +public class GDItemType implements CatalogType { + + private ItemType type; + + public GDItemType(ItemType type) { + this.type = type; + } + + @Override + public String getName() { + return this.type.getName(); + } + + public String getId() { + return this.type.getId(); + } + + public ItemType getType() { + return this.type; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/registry/ItemTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/internal/registry/ItemTypeRegistryModule.java new file mode 100644 index 0000000..8f60776 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/registry/ItemTypeRegistryModule.java @@ -0,0 +1,68 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import org.spongepowered.api.item.ItemType; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public final class ItemTypeRegistryModule { + + protected final Map<String, GDItemType> itemTypeMappings = new HashMap<>(); + private final org.spongepowered.common.registry.type.ItemTypeRegistryModule SPONGE_REGISTRY = org.spongepowered.common.registry.type.ItemTypeRegistryModule.getInstance(); + + public static ItemTypeRegistryModule getInstance() { + return Holder.INSTANCE; + } + + public Optional<GDItemType> getById(String id) { + if (!checkNotNull(id).contains(":")) { + id = "minecraft:" + id; + } + return Optional.ofNullable(this.itemTypeMappings.get(id.toLowerCase(Locale.ENGLISH))); + } + + public Collection<GDItemType> getAll() { + return ImmutableList.copyOf(this.itemTypeMappings.values()); + } + + public void registerDefaults() { + for (ItemType type : SPONGE_REGISTRY.getAll()) { + this.itemTypeMappings.put(type.getId(), new GDItemType(type)); + } + } + + private static final class Holder { + + static final ItemTypeRegistryModule INSTANCE = new ItemTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/util/BlockUtil.java b/sponge/src/main/java/com/griefdefender/internal/util/BlockUtil.java new file mode 100644 index 0000000..63c6b49 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/util/BlockUtil.java @@ -0,0 +1,485 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.util; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.Maps; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.util.BlockPosCache; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.network.play.server.SPacketBlockChange; +import net.minecraft.network.play.server.SPacketChunkData; +import net.minecraft.server.management.PlayerChunkMapEntry; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.MinecraftException; +import net.minecraft.world.WorldServer; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.gen.ChunkProviderServer; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.data.property.block.MatterProperty; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.util.Direction; +import org.spongepowered.api.util.blockray.BlockRay; +import org.spongepowered.api.util.blockray.BlockRayHit; +import org.spongepowered.api.world.LocatableBlock; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.common.bridge.world.chunk.ChunkBridge; +import org.spongepowered.common.bridge.world.chunk.ChunkProviderBridge; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class BlockUtil { + + private static BlockUtil instance; + + static { + instance = new BlockUtil(); + } + + public static BlockUtil getInstance() { + return instance; + } + + public static final Direction[] CARDINAL_SET = { + Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST + }; + public static final Direction[] ORDINAL_SET = { + Direction.NORTH, Direction.NORTHEAST, Direction.EAST, Direction.SOUTHEAST, + Direction.SOUTH, Direction.SOUTHWEST, Direction.WEST, Direction.NORTHWEST, + }; + private static final int NUM_XZ_BITS = 4; + private static final int NUM_SHORT_Y_BITS = 8; + private static final short XZ_MASK = 0xF; + private static final short Y_SHORT_MASK = 0xFF; + + public static final Map<Integer, BlockPosCache> ENTITY_BLOCK_CACHE = new Int2ObjectArrayMap<>(); + private static final Map<BlockState, Integer> BLOCKSTATE_META_CACHE = Maps.newHashMap(); + public static boolean POPULATING_CHUNK = false; + + public String posToString(Location<World> location) { + return posToString(location.getBlockPosition()); + } + + public String posToString(Vector3i pos) { + return posToString(pos.getX(), pos.getY(), pos.getZ()); + } + + public String posToString(int x, int y, int z) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append(x); + stringBuilder.append(";"); + stringBuilder.append(y); + stringBuilder.append(";"); + stringBuilder.append(z); + + return stringBuilder.toString(); + } + + public Vector3i posFromString(String pos) throws Exception { + String[] elements = pos.split(";"); + + if (elements.length < 3) { + throw new Exception("Invalid position " + pos + ""); + } + + String xString = elements[0]; + String yString = elements[1]; + String zString = elements[2]; + + int x = Integer.parseInt(xString); + int y = Integer.parseInt(yString); + int z = Integer.parseInt(zString); + + return new Vector3i(x, y, z); + } + + public boolean clickedClaimCorner(GDClaim claim, Vector3i clickedPos) { + int clickedX = clickedPos.getX(); + int clickedY = clickedPos.getY(); + int clickedZ = clickedPos.getZ(); + int lesserX = claim.getLesserBoundaryCorner().getX(); + int lesserY = claim.getLesserBoundaryCorner().getY(); + int lesserZ = claim.getLesserBoundaryCorner().getZ(); + int greaterX = claim.getGreaterBoundaryCorner().getX(); + int greaterY = claim.getGreaterBoundaryCorner().getY(); + int greaterZ = claim.getGreaterBoundaryCorner().getZ(); + if ((clickedX == lesserX || clickedX == greaterX) && (clickedZ == lesserZ || clickedZ == greaterZ) + && (!claim.isCuboid() || (clickedY == lesserY || clickedY == greaterY))) { + return true; + } + + return false; + } + + public int getBlockStateMeta(BlockState state) { + Integer meta = BLOCKSTATE_META_CACHE.get(state); + if (meta == null) { + Block mcBlock = (net.minecraft.block.Block) state.getType(); + meta = mcBlock.getMetaFromState((IBlockState) state); + BLOCKSTATE_META_CACHE.put(state, meta); + } + return meta; + } + + public int getClaimBlockCost(World world, Vector3i lesser, Vector3i greater, boolean cuboid) { + final int claimWidth = greater.getX() - lesser.getX() + 1; + final int claimHeight = greater.getY() - lesser.getY() + 1; + final int claimLength = greater.getZ() - lesser.getZ() + 1; + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.AREA) { + return claimWidth * claimLength; + } + + return claimLength * claimWidth * claimHeight; + } + + public long asLong(int x, int z) { + return (long)x & 4294967295L | ((long)z & 4294967295L) << 32; + } + + /** + * Serialize this BlockPos into a short value + */ + public short blockPosToShort(Vector3i pos) { + short serialized = (short) setNibble(0, pos.getX() & XZ_MASK, 0, NUM_XZ_BITS); + serialized = (short) setNibble(serialized, pos.getY() & Y_SHORT_MASK, 1, NUM_SHORT_Y_BITS); + serialized = (short) setNibble(serialized, pos.getZ() & XZ_MASK, 3, NUM_XZ_BITS); + return serialized; + } + + private int setNibble(int num, int data, int which, int bitsToReplace) { + return (num & ~(bitsToReplace << (which * 4)) | (data << (which * 4))); + } + + public void restoreClaim(GDClaim claim) { + if (claim.isAdminClaim()) { + return; + } + + // it's too expensive to do this for huge claims + if (claim.getClaimBlocks() > (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME ? 2560000 : 10000)) { + return; + } + + ArrayList<org.spongepowered.api.world.Chunk> chunks = claim.getChunks(); + final List<net.minecraft.world.chunk.Chunk> regeneratedChunks = new ArrayList<>(); + final ChunkProviderServer chunkProviderServer = (ChunkProviderServer) ((WorldServer) claim.getWorld()).getChunkProvider(); + for (org.spongepowered.api.world.Chunk chunk : chunks) { + final Vector3i min = chunk.getBlockMin(); + final Vector3i max = chunk.getBlockMax(); + if (claim.contains(min, true) && claim.contains(max, true)) { + BlockUtil.getInstance().regenerateChunk((net.minecraft.world.chunk.Chunk) chunk); + } else { + regeneratedChunks.add((net.minecraft.world.chunk.Chunk) chunk); + } + } + + for (net.minecraft.world.chunk.Chunk chunk : regeneratedChunks) { + final net.minecraft.world.chunk.Chunk newChunk = chunkProviderServer.chunkGenerator.generateChunk(chunk.x, chunk.z); + final net.minecraft.world.WorldServer world = (WorldServer) chunk.getWorld(); + List<BlockPos> changedPositions = new ArrayList<>(); + if (newChunk != null) { + final int cx = newChunk.x << 4; + final int cz = newChunk.z << 4; + for(int xx = cx; xx < cx + 16; xx++) { + for(int zz = cz; zz < cz + 16; zz++) { + for(int yy = 0; yy < 256; yy++) { + final BlockPos blockPos = new BlockPos(xx, yy, zz); + if (claim.contains(xx, yy, zz, true, null, false)) { + final IBlockState newState = newChunk.getBlockState(blockPos); + final IBlockState oldState = chunk.getBlockState(blockPos); + if (oldState == newState) { + continue; + } + world.removeTileEntity(blockPos); + chunk.setBlockState(blockPos, newState); + changedPositions.add(blockPos); + } + } + } + } + + final PlayerChunkMapEntry playerMapChunkEntry = ((WorldServer) chunk.getWorld()).getPlayerChunkMap().getEntry(chunk.x, chunk.z); + if (playerMapChunkEntry != null) { + List<EntityPlayerMP> chunkPlayers = playerMapChunkEntry.players; + for (EntityPlayerMP playerMP: chunkPlayers) { + for (BlockPos pos : changedPositions) { + playerMP.connection.sendPacket(new SPacketBlockChange(world, pos)); + } + } + } + } + } + } + + private void saveChunkData(ChunkProviderServer chunkProviderServer, Chunk chunkIn) + { + try + { + chunkIn.setLastSaveTime(chunkIn.getWorld().getTotalWorldTime()); + chunkProviderServer.chunkLoader.saveChunk(chunkIn.getWorld(), chunkIn); + } + catch (IOException ioexception) + { + //LOGGER.error((String)"Couldn\'t save chunk", (Throwable)ioexception); + } + catch (MinecraftException minecraftexception) + { + //LOGGER.error((String)"Couldn\'t save chunk; already in use by another instance of Minecraft?", (Throwable)minecraftexception); + } + try + { + chunkProviderServer.chunkLoader.saveExtraChunkData(chunkIn.getWorld(), chunkIn); + } + catch (Exception exception) + { + //LOGGER.error((String)"Couldn\'t save entities", (Throwable)exception); + } + } + + private void unloadChunk(net.minecraft.world.chunk.Chunk chunk) { + ChunkProviderServer chunkProviderServer = (ChunkProviderServer) chunk.getWorld().getChunkProvider(); + + boolean saveChunk = false; + if (chunk.needsSaving(true)) { + saveChunk = true; + } + + chunk.onUnload(); + + if (saveChunk) { + saveChunkData(chunkProviderServer, chunk); + } + + chunkProviderServer.loadedChunks.remove(ChunkPos.asLong(chunk.x, chunk.z)); + ((ChunkBridge) chunk).bridge$setScheduledForUnload(-1); + } + + public boolean regenerateChunk(Chunk chunk) { + // Before unloading the chunk, we copy its entities then clear. + // This ensures that they don't get unloaded when we unload the chunk, + // since we want to keep them. + // + // We explicitly do *not* clear any tileentity-related things - we + // want those to be saved and unloaded, since the new chunk + // will have completely different blocks + List<Entity> entityList = new ArrayList<>(); + for (ClassInheritanceMultiMap<Entity> multiEntityList : chunk.getEntityLists()) { + entityList.addAll(multiEntityList); + } + + for (Entity entity : entityList) { + chunk.removeEntity(entity); + } + + unloadChunk(chunk); + ChunkProviderServer chunkProviderServer = (ChunkProviderServer) chunk.getWorld().getChunkProvider(); + Chunk newChunk = chunkProviderServer.chunkGenerator.generateChunk(chunk.x, chunk.z); + PlayerChunkMapEntry playerChunk = ((WorldServer) chunk.getWorld()).getPlayerChunkMap().getEntry(chunk.x, chunk.z); + if (playerChunk != null) { + playerChunk.chunk = newChunk; + } + + if (newChunk != null) { + WorldServer world = (WorldServer) newChunk.getWorld(); + world.getChunkProvider().loadedChunks.put(ChunkPos.asLong(newChunk.x, newChunk.z), newChunk); + newChunk.onLoad(); + POPULATING_CHUNK = true; + newChunk.populate(world.getChunkProvider(), world.getChunkProvider().chunkGenerator); + POPULATING_CHUNK = false; + for (Entity entity: entityList) { + newChunk.addEntity(entity); + } + refreshChunk(newChunk); + } + + return newChunk != null; + } + + public boolean refreshChunk(Chunk chunk) { + int x = chunk.x; + int z = chunk.z; + WorldServer world = (WorldServer) chunk.getWorld(); + ChunkProviderBridge chunkProviderServer = (ChunkProviderBridge) world.getChunkProvider(); + if (chunkProviderServer.bridge$getLoadedChunkWithoutMarkingActive(x, z) == null) { + return false; + } + + List<EntityPlayerMP> chunkPlayers = ((WorldServer) chunk.getWorld()).getPlayerChunkMap().getEntry(chunk.x, chunk.z).players; + + // We deliberately send two packets, to avoid sending a 'fullChunk' packet + // (a changedSectionFilter of 65535). fullChunk packets cause the client to + // completely overwrite its current chunk with a new chunk instance. This causes + // weird issues, such as making any entities in that chunk invisible (until they leave it + // for a new chunk) + for (EntityPlayerMP playerMP: chunkPlayers) { + playerMP.connection.sendPacket(new SPacketChunkData(chunk, 65534)); + playerMP.connection.sendPacket(new SPacketChunkData(chunk, 1)); + /*for (ClassInheritanceMultiMap<Entity> multiEntityList : chunk.getEntityLists()) { + for (Entity entity : multiEntityList) { + if (entity != playerMP) { + final EntityTracker entityTracker = world.getEntityTracker(); + final EntityTrackerEntry lookup = entityTracker.trackedEntityHashTable.lookup(entity.getEntityId()); + Packet<?> newPacket = lookup.createSpawnPacket(); + playerMP.connection.sendPacket(newPacket); + } + } + }*/ + } + + return true; + } + + private int directionToIndex(Direction direction) { + switch (direction) { + case NORTH: + case NORTHEAST: + case NORTHWEST: + return 0; + case SOUTH: + case SOUTHEAST: + case SOUTHWEST: + return 1; + case EAST: + return 2; + case WEST: + return 3; + default: + throw new IllegalArgumentException("Unexpected direction"); + } + } + + public Optional<Location<World>> getTargetBlock(Player player, GDPlayerData playerData, int maxDistance, boolean ignoreAir) throws IllegalStateException { + BlockRay<World> blockRay = BlockRay.from(player).distanceLimit(maxDistance).build(); + GDClaim claim = null; + if (playerData.visualClaimId != null) { + claim = (GDClaim) GriefDefenderPlugin.getInstance().dataStore.getClaim(player.getWorld().getUniqueId(), playerData.visualClaimId); + } + + while (blockRay.hasNext()) { + BlockRayHit<World> blockRayHit = blockRay.next(); + if (claim != null) { + for (Vector3i corner : claim.getVisualizer().getVisualCorners()) { + if (corner.equals(blockRayHit.getBlockPosition())) { + return Optional.of(blockRayHit.getLocation()); + } + } + } + if (ignoreAir) { + if (blockRayHit.getLocation().getBlockType() != BlockTypes.TALLGRASS) { + return Optional.of(blockRayHit.getLocation()); + } + } else { + if (blockRayHit.getLocation().getBlockType() != BlockTypes.AIR && + blockRayHit.getLocation().getBlockType() != BlockTypes.TALLGRASS) { + return Optional.of(blockRayHit.getLocation()); + } + } + } + + return Optional.empty(); + } + + public boolean isLiquidSource(Object source) { + if (source instanceof BlockSnapshot) { + final BlockSnapshot blockSnapshot = (BlockSnapshot) source; + MatterProperty matterProperty = blockSnapshot.getState().getProperty(MatterProperty.class).orElse(null); + if (matterProperty != null && matterProperty.getValue() == MatterProperty.Matter.LIQUID) { + return true; + } + } + if (source instanceof LocatableBlock) { + final LocatableBlock locatableBlock = (LocatableBlock) source; + MatterProperty matterProperty = locatableBlock.getBlockState().getProperty(MatterProperty.class).orElse(null); + if (matterProperty != null && matterProperty.getValue() == MatterProperty.Matter.LIQUID) { + return true; + } + } + return false; + } + + public Set<Claim> getNearbyClaims(Location<World> location) { + return getNearbyClaims(location, 50); + } + + public Set<Claim> getNearbyClaims(Location<World> location, int blockDistance) { + Set<Claim> claims = new HashSet<>(); + GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(location.getExtent().getUniqueId()); + if (claimWorldManager == null) { + return claims; + } + + org.spongepowered.api.world.Chunk lesserChunk = location.getExtent().getChunkAtBlock(location.sub(blockDistance, 0, blockDistance).getBlockPosition()).orElse(null); + org.spongepowered.api.world.Chunk greaterChunk = location.getExtent().getChunkAtBlock(location.add(blockDistance, 0, blockDistance).getBlockPosition()).orElse(null); + + if (lesserChunk != null && greaterChunk != null) { + for (int chunkX = lesserChunk.getPosition().getX(); chunkX <= greaterChunk.getPosition().getX(); chunkX++) { + for (int chunkZ = lesserChunk.getPosition().getZ(); chunkZ <= greaterChunk.getPosition().getZ(); chunkZ++) { + org.spongepowered.api.world.Chunk chunk = location.getExtent().getChunk(chunkX, 0, chunkZ).orElse(null); + if (chunk != null) { + Set<Claim> claimsInChunk = claimWorldManager.getInternalChunksToClaimsMap().get(ChunkPos.asLong(chunkX, chunkZ)); + if (claimsInChunk != null) { + for (Claim claim : claimsInChunk) { + final GDClaim gpClaim = (GDClaim) claim; + if (gpClaim.parent == null && !claims.contains(claim)) { + claims.add(claim); + } + } + } + } + } + } + } + + return claims; + } + + public boolean isBlockWater(BlockState state) { + Optional<MatterProperty> matterProperty = state.getProperty(MatterProperty.class); + if (matterProperty.isPresent() && matterProperty.get().getValue() == MatterProperty.Matter.LIQUID) { + return true; + } + return false; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/util/NMSUtil.java b/sponge/src/main/java/com/griefdefender/internal/util/NMSUtil.java new file mode 100644 index 0000000..f6ddffd --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/util/NMSUtil.java @@ -0,0 +1,388 @@ +package com.griefdefender.internal.util; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.internal.EntityRemovalListener; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.util.EntityUtils; +import net.minecraft.block.BlockBasePressurePlate; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.ContainerChest; +import net.minecraft.inventory.ContainerPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemFood; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.entity.damage.DamageType; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.item.inventory.Container; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.item.inventory.ItemStackSnapshot; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.profile.GameProfile; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.common.SpongeImplHooks; +import org.spongepowered.common.entity.SpongeEntityType; +import org.spongepowered.common.item.inventory.custom.CustomInventory; +import org.spongepowered.common.util.SpongeUsernameCache; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +public class NMSUtil { + + public static final BiMap<String, EnumCreatureType> SPAWN_TYPES = HashBiMap.create(); + + static { + SPAWN_TYPES.put("ambient", EnumCreatureType.AMBIENT); + SPAWN_TYPES.put("animal", EnumCreatureType.CREATURE); + SPAWN_TYPES.put("aquatic", EnumCreatureType.WATER_CREATURE); + SPAWN_TYPES.put("monster", EnumCreatureType.MONSTER); + } + + private static NMSUtil instance; + + static { + instance = new NMSUtil(); + } + + public static NMSUtil getInstance() { + return instance; + } + + public void addEntityRemovalListener(World world) { + ((net.minecraft.world.WorldServer) world).addEventListener(new EntityRemovalListener()); + } + + public boolean isTransparent(BlockState state) { + final IBlockState iblockstate = (IBlockState)(Object) state; + return !iblockstate.isOpaqueCube(); + } + + public int getItemDamage(ItemStack stack) { + return ((net.minecraft.item.ItemStack)(Object) stack).getItemDamage(); + } + + public String getEntityId(Entity targetEntity, boolean isSource) { + net.minecraft.entity.Entity mcEntity = null; + if (targetEntity instanceof net.minecraft.entity.Entity) { + mcEntity = (net.minecraft.entity.Entity) targetEntity; + } + String id = ""; + if (mcEntity != null && mcEntity instanceof EntityItem) { + EntityItem mcItem = (EntityItem) mcEntity; + net.minecraft.item.ItemStack itemStack = mcItem.getItem(); + if (itemStack != null && itemStack.getItem() != null) { + ItemType itemType = (ItemType) itemStack.getItem(); + id = itemType.getId() + "." + itemStack.getItemDamage(); + } + } else { + if (targetEntity.getType() != null) { + id = targetEntity.getType().getId(); + } + + } + + if (mcEntity != null && id.contains("unknown") && SpongeImplHooks.isFakePlayer(mcEntity)) { + final String modId = SpongeImplHooks.getModIdFromClass(mcEntity.getClass()); + id = modId + ":fakeplayer_" + EntityUtils.getFriendlyName(mcEntity).toLowerCase(); + } else if (id.equals("unknown:unknown") && targetEntity instanceof EntityPlayer) { + id = "minecraft:player"; + } + + if (mcEntity != null && targetEntity instanceof Living) { + String[] parts = id.split(":"); + if (parts.length > 1) { + final String modId = parts[0]; + String name = parts[1]; + if (modId.equalsIgnoreCase("pixelmon") && modId.equalsIgnoreCase(name)) { + name = EntityUtils.getFriendlyName(mcEntity).toLowerCase(); + GDPermissionManager.getInstance().populateEventSourceTarget(modId + ":" + name, isSource); + if (isSource) { + id = modId + ":" + name; + return id; + } + } + if (!isSource) { + for (EnumCreatureType type : EnumCreatureType.values()) { + if (SpongeImplHooks.isCreatureOfType(mcEntity, type)) { + id = modId + ":" + SPAWN_TYPES.inverse().get(type) + ":" + name; + break; + } + } + } + } + } + + if (targetEntity instanceof Item) { + id = ((Item) targetEntity).getItemType().getId(); + } + + return id; + } + + public int getEntityMinecraftId(Entity entity) { + return ((net.minecraft.entity.Entity) entity).getEntityId(); + } + + public String getEntityName(Entity entity) { + return ((net.minecraft.entity.Entity) entity).getName(); + } + + // Fallback option for creating user's from usernamecache.json + public User createUserFromCache(UUID uuid) { + final String username = SpongeUsernameCache.getLastKnownUsername(uuid); + if (username != null) { + return Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(GameProfile.of(uuid, username)).orElse(null); + } + return null; + } + + public User createUserFromCache(String username) { + final UUID uuid = SpongeUsernameCache.getLastKnownUUID(username); + if (username != null) { + return Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(GameProfile.of(uuid, username)).orElse(null); + } + return null; + } + + public String getItemStackId(ItemStack stack) { + if (stack.getType() instanceof ItemBlock) { + ItemBlock itemBlock = (ItemBlock) stack.getType(); + net.minecraft.item.ItemStack nmsStack = (net.minecraft.item.ItemStack)(Object) stack; + BlockState blockState = ((BlockState) itemBlock.getBlock().getStateFromMeta(nmsStack.getItemDamage())); + return blockState.getType().getId(); + } + + return stack.getType().getId(); + } + + public String getItemStackMeta(ItemStack stack) { + return String.valueOf(((net.minecraft.item.ItemStack)(Object) stack).getItemDamage()); + } + + public int getPlayerBlockReachDistance(Player player) { + return (int) SpongeImplHooks.getBlockReachDistance((EntityPlayerMP) player); + } + + public boolean isBlockPressurePlate(BlockType type) { + return type instanceof BlockBasePressurePlate; + } + + public boolean isEntityMonster(Entity entity) { + return SpongeImplHooks.isCreatureOfType((net.minecraft.entity.Entity) entity, EnumCreatureType.MONSTER); + } + + public boolean isEntityAnimal(Entity entity) { + return SpongeImplHooks.isCreatureOfType((net.minecraft.entity.Entity) entity, EnumCreatureType.CREATURE); + } + + public boolean isFakePlayer(Entity entity) { + if (!(entity instanceof EntityPlayer)) { + return false; + } + return SpongeImplHooks.isFakePlayer((EntityPlayer) entity); + } + + public boolean isContainerCustomInventory(Container container) { + if (container instanceof ContainerChest) { + return ((ContainerChest) container).getLowerChestInventory() instanceof CustomInventory; + } + return false; + } + + public boolean isItemFood(ItemType type) { + return type instanceof ItemFood; + } + + public boolean isItemBlock(ItemStack stack) { + return stack instanceof ItemBlock; + } + + public boolean containsInventory(Object object) { + return object instanceof IInventory; + } + + public boolean containsContainerPlayer(Cause cause) { + return cause.containsType(ContainerPlayer.class); + } + + public void closePlayerScreen(Player player) { + ((EntityPlayerMP) player).closeScreen(); + } + + + public RayTraceResult rayTracePlayerEyes(EntityPlayerMP player) { + double distance = SpongeImplHooks.getBlockReachDistance(player) + 1; + Vec3d startPos = new Vec3d(player.posX, player.posY + player.getEyeHeight(), player.posZ); + Vec3d endPos = startPos.add(new Vec3d(player.getLookVec().x * distance, player.getLookVec().y * distance, player.getLookVec().z * distance)); + return player.world.rayTraceBlocks(startPos, endPos); + } + + public Vec3d rayTracePlayerEyeHitVec(EntityPlayerMP player) { + RayTraceResult result = rayTracePlayerEyes(player); + return result == null ? null : result.hitVec; + } + + public String getEntitySpawnFlag(Flag flag, String target) { + if (flag.getName().contains("entity") || flag == Flags.ITEM_SPAWN || flag == Flags.ENTER_CLAIM || flag == Flags.EXIT_CLAIM) { + String claimFlag = flag.getName(); + // first check for valid SpawnType + String[] parts = target.split(":"); + EnumCreatureType type = SPAWN_TYPES.get(parts[1]); + if (type != null) { + claimFlag += "." + parts[0] + "." + SPAWN_TYPES.inverse().get(type); + return claimFlag; + } else { + Optional<EntityType> entityType = Sponge.getRegistry().getType(EntityType.class, target); + if (entityType.isPresent()) { + SpongeEntityType spongeEntityType = (SpongeEntityType) entityType.get(); + if (spongeEntityType.getEnumCreatureType() != null) { + String creatureType = SPAWN_TYPES.inverse().get(spongeEntityType.getEnumCreatureType()); + if (parts[1].equalsIgnoreCase("pixelmon")) { + claimFlag += "." + parts[0] + ".animal"; + } else { + claimFlag += "." + parts[0] + "." + creatureType + "." + parts[1]; + } + return claimFlag; + } else { + claimFlag += "." + parts[0] + "." + parts[1]; + return claimFlag; + } + } + // Unfortunately this is required until Pixelmon registers their entities correctly in FML + if (target.contains("pixelmon")) { + // If target was not found in registry, assume its a pixelmon animal + if (parts[1].equalsIgnoreCase("pixelmon")) { + claimFlag += "." + parts[0] + ".animal"; + } else { + claimFlag += "." + parts[0] + ".animal." + parts[1]; + } + return claimFlag; + } + } + } + + return null; + } + + public void populateTabComplete() { + for (BlockType blockType : Sponge.getRegistry().getAllOf(BlockType.class)) { + String modId = blockType.getId().split(":")[0].toLowerCase(); + if (modId.equals("none")) { + continue; + } + final String blockTypeId = blockType.getId().toLowerCase(); + if (!GriefDefenderPlugin.ID_MAP.contains(modId + ":any")) { + GriefDefenderPlugin.ID_MAP.add(modId + ":any"); + } + GriefDefenderPlugin.ID_MAP.add(blockTypeId); + } + + for (EntityType entityType : Sponge.getRegistry().getAllOf(EntityType.class)) { + String modId = entityType.getId().split(":")[0].toLowerCase(); + if (modId.equals("none")) { + continue; + } + final String entityTypeId = entityType.getId().toLowerCase(); + if (!GriefDefenderPlugin.ID_MAP.contains(modId + ":any")) { + GriefDefenderPlugin.ID_MAP.add(modId + ":any"); + } + if (!GriefDefenderPlugin.ID_MAP.contains(entityTypeId)) { + GriefDefenderPlugin.ID_MAP.add(entityTypeId); + } + if (!GriefDefenderPlugin.ID_MAP.contains(modId + ":animal") && Living.class.isAssignableFrom(entityType.getEntityClass())) { + GriefDefenderPlugin.ID_MAP.add(modId + ":ambient"); + GriefDefenderPlugin.ID_MAP.add(modId + ":animal"); + GriefDefenderPlugin.ID_MAP.add(modId + ":aquatic"); + GriefDefenderPlugin.ID_MAP.add(modId + ":monster"); + } + } + + for (ItemType itemType : Sponge.getRegistry().getAllOf(ItemType.class)) { + String modId = itemType.getId().split(":")[0].toLowerCase(); + if (modId.equals("none")) { + continue; + } + final String itemTypeId = itemType.getId().toLowerCase(); + if (!GriefDefenderPlugin.ID_MAP.contains(modId + ":any")) { + GriefDefenderPlugin.ID_MAP.add(modId + ":any"); + } + if (!GriefDefenderPlugin.ID_MAP.contains(itemTypeId)) { + GriefDefenderPlugin.ID_MAP.add(itemTypeId); + } + GriefDefenderPlugin.ITEM_IDS.add(itemTypeId); + } + + for (DamageType damageType : Sponge.getRegistry().getAllOf(DamageType.class)) { + String damageTypeId = damageType.getId().toLowerCase(); + if (!damageType.getId().contains(":")) { + damageTypeId = "minecraft:" + damageTypeId; + } + if (!GriefDefenderPlugin.ID_MAP.contains(damageType.getId())) { + GriefDefenderPlugin.ID_MAP.add(damageTypeId); + } + } + // commands + Set<? extends CommandMapping> commandList = Sponge.getCommandManager().getCommands(); + for (CommandMapping command : commandList) { + PluginContainer pluginContainer = Sponge.getCommandManager().getOwner(command).orElse(null); + if (pluginContainer != null) { + for (String alias : command.getAllAliases()) { + String[] parts = alias.split(":"); + if (parts.length > 1) { + GriefDefenderPlugin.ID_MAP.add(alias); + } + } + } + } + } + + public GDClaim createClaimFromCenter(Location<World> center, float radius) { + AxisAlignedBB aabb = new AxisAlignedBB(VecHelper.toBlockPos(center)); + aabb = aabb.grow(radius); + final Vector3i lesser = new Vector3i(aabb.minX, aabb.minY, aabb.minZ); + final Vector3i greater = new Vector3i(aabb.maxX, aabb.maxY, aabb.maxZ); + return new GDClaim(center.getExtent(), lesser, greater, ClaimTypes.BASIC, false); + } + + public ItemStack getActiveItem(Player player) { + return this.getActiveItem(player, null); + } + + public ItemStack getActiveItem(Player player, Event currentEvent) { + final ItemStackSnapshot snapshot = player.get(Keys.ACTIVE_ITEM).orElse(null); + if (snapshot != null) { + return snapshot.createStack(); + } + return null; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/util/VecHelper.java b/sponge/src/main/java/com/griefdefender/internal/util/VecHelper.java new file mode 100644 index 0000000..55d15d0 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/util/VecHelper.java @@ -0,0 +1,202 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.util; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3d; +import com.flowpowered.math.vector.Vector3i; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.Rotations; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +public final class VecHelper { + + public static Location<World> toLocation(World world, Vector3i pos) { + return new Location<World>(world, pos.getX(), pos.getY(), pos.getZ()); + } + + public static Location<World> toLocation(net.minecraft.world.World world, BlockPos pos) { + return new Location<World>((World) world, pos.getX(), pos.getY(), pos.getZ()); + } + + // === Flow Vector3d --> BlockPos === + + public static BlockPos toBlockPos(Vector3d vector) { + if (vector == null) { + return null; + } + return new BlockPos(vector.getX(), vector.getY(), vector.getZ()); + } + + // === Flow Vector3i --> BlockPos === + + public static BlockPos toBlockPos(Vector3i vector) { + if (vector == null) { + return null; + } + return new BlockPos(vector.getX(), vector.getY(), vector.getZ()); + } + + // === Location<World> --> Vector3i === + public static Vector3i toVector3i(Location<World> location) { + if (location == null) { + return null; + } + return new Vector3i(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + // === SpongeAPI Location<World> --> BlockPos === + public static BlockPos toBlockPos(Location<World> location) { + if (location == null) { + return null; + } + return new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + } + // === MC BlockPos --> Flow Vector3i == + + public static Vector3i toVector3i(BlockPos pos) { + if (pos == null) { + return null; + } + return new Vector3i(pos.getX(), pos.getY(), pos.getZ()); + } + + // === MC BlockPos --> Flow Vector3d == + + public static Vector3d toVector3d(BlockPos pos) { + if (pos == null) { + return null; + } + return new Vector3d(pos.getX(), pos.getY(), pos.getZ()); + } + + // === Rotations --> Flow Vector === + + public static Vector3d toVector3d(Rotations rotation) { + if (rotation == null) { + return null; + } + return new Vector3d(rotation.getX(), rotation.getY(), rotation.getZ()); + } + + // === MC Vec3i --> Flow Vector3i === + + public static Vector3i toVector3i(Vec3i vector) { + if (vector == null) { + return null; + } + return new Vector3i(vector.getX(), vector.getY(), vector.getZ()); + } + + // === Flow Vector3i --> MC Vec3i === + + public static Vec3i toVec3i(Vector3i vector) { + if (vector == null) { + return null; + } + return new Vec3i(vector.getX(), vector.getY(), vector.getZ()); + } + + // === Flow Vector3d --> MC Vec3d === + + public static Vec3d toVec3d(Vector3d vector) { + if (vector == null) { + return null; + } + return new Vec3d(vector.getX(), vector.getY(), vector.getZ()); + } + + // === MC BlockPos --> MC Vec3d + + public static Vec3d toVec3d(BlockPos pos) { + if (pos == null) { + return null; + } + return new Vec3d(pos.getX(), pos.getY(), pos.getZ()); + } + + // === MC ChunkCoordIntPair ---> Flow Vector3i === + + public static Vector3i toVec3i(ChunkPos pos) { + if (pos == null) { + return null; + } + return new Vector3i(pos.x, 0, pos.z); + } + + // === Flow Vector3i --> MC ChunkCoordIntPair === + + public static ChunkPos toChunkPos(Vector3i vector) { + if (vector == null) { + return null; + } + return new ChunkPos(vector.getX(), vector.getZ()); + } + + // === MC Vec3 --> flow Vector3d == + + public static Vector3d toVector3d(Vec3d vector) { + if (vector == null) { + return null; + } + return new Vector3d(vector.x, vector.y, vector.z); + } + + // === Flow Vector3d --> MC Vec3 == + + public static Vec3i toVec3i(Vector3d vector) { + if (vector == null) { + return null; + } + return new Vec3i(vector.getX(), vector.getY(), vector.getZ()); + } + + // === Flow Vector --> Rotations === + public static Rotations toRotation(Vector3d vector) { + if (vector == null) { + return null; + } + return new Rotations((float) vector.getX(), (float) vector.getY(), (float) vector.getZ()); + } + + public static boolean inBounds(int x, int y, Vector2i min, Vector2i max) { + return x >= min.getX() && x <= max.getX() && y >= min.getY() && y <= max.getY(); + } + + public static boolean inBounds(int x, int y, int z, Vector3i min, Vector3i max) { + return x >= min.getX() && x <= max.getX() && y >= min.getY() && y <= max.getY() && z >= min.getZ() && z <= max.getZ(); + } + + public static boolean inBounds(Vector3d pos, Vector3i min, Vector3i max) { + return inBounds(pos.getX(), pos.getY(), pos.getZ(), min, max); + } + + public static boolean inBounds(double x, double y, double z, Vector3i min, Vector3i max) { + return x >= min.getX() && x <= max.getX() && y >= min.getY() && y <= max.getY() && z >= min.getZ() && z <= max.getZ(); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/internal/visual/ClaimVisual.java b/sponge/src/main/java/com/griefdefender/internal/visual/ClaimVisual.java new file mode 100644 index 0000000..f775596 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/visual/ClaimVisual.java @@ -0,0 +1,559 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.visual; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.task.ClaimVisualApplyTask; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.data.Transaction; +import org.spongepowered.api.data.property.block.MatterProperty; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.util.Direction; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class ClaimVisual { + + public static GDClaimVisualType ADMIN; + public static GDClaimVisualType BASIC; + public static GDClaimVisualType DEFAULT; + public static GDClaimVisualType ERROR; + public static GDClaimVisualType SUBDIVISION; + public static GDClaimVisualType RESTORENATURE; + public static GDClaimVisualType TOWN; + + private List<Transaction<BlockSnapshot>> visualTransactions; + private List<Transaction<BlockSnapshot>> newVisuals; + private List<Vector3i> corners; + private GDClaimVisualType type; + private GDClaim claim; + private Vector3i lesserBoundaryCorner; + private Vector3i greaterBoundaryCorner; + private boolean cuboidVisual = false; + private boolean isPlayerInWater = false; + private int sx, sy, sz; + private int bx, by, bz; + private int minx, minz; + private int maxx, maxz; + private BlockType cornerMaterial; + private BlockType accentMaterial; + private BlockType fillerMaterial; // used for 3d cuboids + private BlockSnapshot.Builder snapshotBuilder; + public boolean displaySubdivisions = false; + private int STEP = 10; + + static { + ADMIN = new GDClaimVisualType("griefdefender:admin", "admin"); + BASIC = new GDClaimVisualType("griefdefender:basic", "basic"); + DEFAULT = new GDClaimVisualType("griefdefender:default", "default"); + ERROR = new GDClaimVisualType("griefdefender:error", "error"); + RESTORENATURE = new GDClaimVisualType("griefdefender:restorenature", "restorenature"); + SUBDIVISION = new GDClaimVisualType("griefdefender:subdivision", "subdivision"); + TOWN = new GDClaimVisualType("griefdefender:town", "town"); + } + + public ClaimVisual(GDClaimVisualType type) { + initBlockVisualTypes(type); + this.type = type; + this.snapshotBuilder = Sponge.getGame().getRegistry().createBuilder(BlockSnapshot.Builder.class); + this.visualTransactions = new ArrayList<Transaction<BlockSnapshot>>(); + this.newVisuals = new ArrayList<>(); + this.corners = new ArrayList<>(); + } + + public ClaimVisual(GDClaim claim, GDClaimVisualType type) { + this(claim.lesserBoundaryCorner, claim.greaterBoundaryCorner, type); + this.claim = claim; + } + + public ClaimVisual(Vector3i lesserBoundaryCorner, Vector3i greaterBoundaryCorner, GDClaimVisualType type) { + initBlockVisualTypes(type); + this.lesserBoundaryCorner = lesserBoundaryCorner; + this.greaterBoundaryCorner = greaterBoundaryCorner; + this.type = type; + this.snapshotBuilder = Sponge.getGame().getRegistry().createBuilder(BlockSnapshot.Builder.class); + this.visualTransactions = new ArrayList<Transaction<BlockSnapshot>>(); + this.newVisuals = new ArrayList<>(); + this.corners = new ArrayList<>(); + } + + public void initBlockVisualTypes(GDClaimVisualType type) { + if (type == BASIC) { + cornerMaterial = BASIC.getVisualCornerBlock(); + accentMaterial = BASIC.getVisualAccentBlock(); + fillerMaterial = BASIC.getVisualFillerBlock(); + } else if (type == ADMIN) { + cornerMaterial = ADMIN.getVisualCornerBlock(); + accentMaterial = ADMIN.getVisualAccentBlock(); + fillerMaterial = ADMIN.getVisualFillerBlock(); + } else if (type == SUBDIVISION) { + cornerMaterial = SUBDIVISION.getVisualCornerBlock(); + accentMaterial = SUBDIVISION.getVisualAccentBlock(); + fillerMaterial = SUBDIVISION.getVisualFillerBlock(); + } else if (type == RESTORENATURE) { + cornerMaterial = RESTORENATURE.getVisualCornerBlock(); + accentMaterial = RESTORENATURE.getVisualAccentBlock(); + } else if (type == TOWN) { + cornerMaterial = TOWN.getVisualCornerBlock(); + accentMaterial = TOWN.getVisualAccentBlock(); + fillerMaterial = TOWN.getVisualFillerBlock(); + } else { + cornerMaterial = DEFAULT.getVisualCornerBlock(); + accentMaterial = DEFAULT.getVisualAccentBlock(); + fillerMaterial = DEFAULT.getVisualFillerBlock(); + } + } + + public static GDClaimVisualType getClaimVisualType(GDClaim claim) { + ClaimType type = claim.getType(); + if (type != null) { + if (type == ClaimTypes.ADMIN) { + return ADMIN; + } else if (type == ClaimTypes.TOWN) { + return TOWN; + } else if (type == ClaimTypes.SUBDIVISION) { + return SUBDIVISION; + } + } + + return BASIC; + } + + public GDClaim getClaim() { + return this.claim; + } + + public void apply(Player player) { + this.apply(player, true); + } + + public void apply(Player player, boolean resetActive) { + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + + // if he has any current visualization, clear it first + //playerData.revertActiveVisual(player); + + if (player.isOnline() && this.visualTransactions.size() > 0 + && this.visualTransactions.get(0).getOriginal().getLocation().get().getExtent().equals(player.getWorld())) { + Sponge.getGame().getScheduler().createTaskBuilder().delayTicks(1L) + .execute(new ClaimVisualApplyTask(player, playerData, this, resetActive)).submit(GDBootstrap.getInstance()); + //GriefDefenderPlugin.getInstance().executor.execute(new VisualizationApplicationTask(player, playerData, this, resetActive)); + } + } + + public void revert(Player player) { + if (!player.isOnline()) { + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + + int minx = player.getLocation().getBlockX() - 100; + int minz = player.getLocation().getBlockZ() - 100; + int maxx = player.getLocation().getBlockX() + 100; + int maxz = player.getLocation().getBlockZ() + 100; + + if (!this.cuboidVisual) { + this.removeElementsOutOfRange(this.visualTransactions, minx, minz, maxx, maxz); + } + + for (int i = 0; i < this.visualTransactions.size(); i++) { + BlockSnapshot snapshot = this.visualTransactions.get(i).getOriginal(); + + if (i == 0) { + if (!player.getWorld().equals(snapshot.getLocation().get().getExtent())) { + return; + } + } + + player.sendBlockChange(snapshot.getPosition(), snapshot.getState()); + } + + playerData.visualBlocks = null; + if (playerData.visualRevertTask != null) { + playerData.visualRevertTask.cancel(); + } + } + + public static ClaimVisual fromClick(Location<World> location, int height, GDClaimVisualType visualizationType, Player player, GDPlayerData playerData) { + ClaimVisual visualization = new ClaimVisual(visualizationType); + visualization.cornerMaterial = GriefDefenderPlugin.getInstance().createVisualBlock.getType(); + BlockSnapshot blockClicked = + visualization.snapshotBuilder.from(location).blockState(visualization.cornerMaterial.getDefaultState()).build(); + visualization.visualTransactions.add(new Transaction<BlockSnapshot>(blockClicked.getLocation().get().createSnapshot(), blockClicked)); + if (GriefDefenderPlugin.getInstance().worldEditProvider != null && playerData.getClaimCreateMode() == CreateModeTypes.VOLUME && GriefDefenderPlugin.getGlobalConfig().getConfig().visual.hideDrag2d) { + GriefDefenderPlugin.getInstance().worldEditProvider.sendVisualDrag(player, playerData, location.getBlockPosition()); + } + return visualization; + } + + public void resetVisuals() { + this.visualTransactions.clear(); + this.newVisuals.clear(); + } + + public void createClaimBlockVisualWithType(GDClaim claim, int height, Location<World> locality, GDPlayerData playerData, GDClaimVisualType visualType) { + this.type = visualType; + this.claim = claim; + this.addClaimElements(height, locality, playerData); + } + + public void createClaimBlockVisuals(int height, Location<World> locality, GDPlayerData playerData) { + if (this.visualTransactions.size() != 0) { + return; + } + + this.addClaimElements(height, locality, playerData); + } + + public GDClaimVisualType getType() { + return this.type; + } + + public void setType(GDClaimVisualType type) { + this.type = type; + } + + private void addClaimElements(int height, Location<World> locality, GDPlayerData playerData) { + this.initBlockVisualTypes(type); + Vector3i lesser = this.claim.getLesserBoundaryCorner(); + Vector3i greater = this.claim.getGreaterBoundaryCorner(); + World world = locality.getExtent(); + boolean liquidTransparent = locality.getBlock().getType().getProperty(MatterProperty.class).isPresent() ? false : true; + + this.sx = lesser.getX(); + this.sy = this.useCuboidVisual() ? lesser.getY() : 0; + this.sz = lesser.getZ(); + this.bx = greater.getX(); + this.by = this.useCuboidVisual() ? greater.getY() : 0; + this.bz = greater.getZ(); + this.minx = this.claim.cuboid ? this.sx : locality.getBlockX() - 75; + this.minz = this.claim.cuboid ? this.sz : locality.getBlockZ() - 75; + this.maxx = this.claim.cuboid ? this.bx : locality.getBlockX() + 75; + this.maxz = this.claim.cuboid ? this.bz : locality.getBlockZ() + 75; + + if (this.sx == this.bx && this.sy == this.by && this.sz == this.bz) { + BlockSnapshot blockClicked = + snapshotBuilder.from(new Location<World>(world, this.sx, this.sy, this.sz)).blockState(this.cornerMaterial.getDefaultState()).build(); + visualTransactions.add(new Transaction<BlockSnapshot>(blockClicked.getLocation().get().createSnapshot(), blockClicked)); + return; + } + + // check CUI support + if (GriefDefenderPlugin.getInstance().worldEditProvider != null && playerData != null + && GriefDefenderPlugin.getInstance().worldEditProvider.hasCUISupport(playerData.getPlayerName())) { + playerData.showVisualFillers = false; + STEP = 0; + } + + // Check if player is in water + final Player player = Sponge.getCauseStackManager().getCurrentCause().first(Player.class).orElse(null); + if (player != null) { + if (BlockUtil.getInstance().isBlockWater(player.getLocation().getBlock())) { + this.isPlayerInWater = true; + } else { + this.isPlayerInWater = false; + } + } else { + this.isPlayerInWater = false; + } + + if (this.useCuboidVisual()) { + this.addVisuals3D(claim, playerData); + } else { + this.addVisuals2D(claim, height); + } + } + + public void addVisuals3D(GDClaim claim, GDPlayerData playerData) { + final World world = claim.getWorld(); + + this.addTopLine(world, this.sy, this.cornerMaterial, this.accentMaterial); + this.addTopLine(world, this.by, this.cornerMaterial, this.accentMaterial); + this.addBottomLine(world, this.sy, this.cornerMaterial, this.accentMaterial); + this.addBottomLine(world, this.by, this.cornerMaterial, this.accentMaterial); + this.addLeftLine(world, this.sy, this.cornerMaterial, this.accentMaterial); + this.addLeftLine(world, this.by, this.cornerMaterial, this.accentMaterial); + this.addRightLine(world, this.sy, this.cornerMaterial, this.accentMaterial); + this.addRightLine(world, this.by, this.cornerMaterial, this.accentMaterial); + // don't show corners while subdividing + if (playerData == null || (playerData.claimSubdividing == null)) { + // top corners + this.addCorners(world, this.by - 1, this.accentMaterial); + // bottom corners + this.addCorners(world, this.sy + 1, this.accentMaterial); + } + + if (STEP != 0 && (playerData == null || playerData.showVisualFillers)) { + for (int y = this.sy + STEP; y < this.by - STEP / 2; y += STEP) { + this.addTopLine(world, y, fillerMaterial, fillerMaterial); + } + for (int y = this.sy + STEP; y < this.by - STEP / 2; y += STEP) { + this.addBottomLine(world, y, fillerMaterial, fillerMaterial); + } + for (int y = this.sy + STEP; y < this.by - STEP / 2; y += STEP) { + this.addLeftLine(world, y, fillerMaterial, fillerMaterial); + } + for (int y = this.sy + STEP; y < this.by - STEP / 2; y += STEP) { + this.addRightLine(world, y, fillerMaterial, fillerMaterial); + } + } + this.visualTransactions.addAll(newVisuals); + } + + public void addVisuals2D(GDClaim claim, int height) { + final World world = claim.getWorld(); + this.addTopLine(world, height, this.cornerMaterial, this.accentMaterial); + this.addBottomLine(world, height, this.cornerMaterial, this.accentMaterial); + this.addLeftLine(world, height, this.cornerMaterial, this.accentMaterial); + this.addRightLine(world, height, this.cornerMaterial, this.accentMaterial); + + this.removeElementsOutOfRange(this.newVisuals, this.minx, this.minz, this.maxx, this.maxz); + + for (int i = 0; i < this.newVisuals.size(); i++) { + final BlockSnapshot element = this.newVisuals.get(i).getFinal(); + if (!claim.contains(element.getPosition())) { + this.newVisuals.remove(i); + } + } + + ArrayList<Transaction<BlockSnapshot>> actualElements = new ArrayList<Transaction<BlockSnapshot>>(); + for (Transaction<BlockSnapshot> element : this.newVisuals) { + Location<World> tempLocation = element.getFinal().getLocation().get(); + Location<World> visibleLocation = + getVisibleLocation(tempLocation.getExtent(), tempLocation.getBlockX(), height, tempLocation.getBlockZ()); + element = new Transaction<BlockSnapshot>(element.getOriginal().withLocation(visibleLocation).withState(visibleLocation.getBlock()), + element.getFinal().withLocation(visibleLocation)); + height = element.getFinal().getPosition().getY(); + actualElements.add(element); + } + + this.visualTransactions.addAll(actualElements); + } + + public void addCorners(World world, int y, BlockType accentMaterial) { + BlockSnapshot corner1 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.bz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(corner1.getLocation().get().createSnapshot(), corner1)); + BlockSnapshot corner2 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.bz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(corner2.getLocation().get().createSnapshot(), corner2)); + BlockSnapshot corner3 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.sz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(corner3.getLocation().get().createSnapshot(), corner3)); + BlockSnapshot corner4 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.sz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(corner4.getLocation().get().createSnapshot(), corner4)); + } + + public void addTopLine(World world, int y, BlockType cornerMaterial, BlockType accentMaterial) { + BlockSnapshot topVisualBlock1 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.bz)).blockState(cornerMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(topVisualBlock1.getLocation().get().createSnapshot(), topVisualBlock1)); + this.corners.add(topVisualBlock1.getPosition()); + BlockSnapshot topVisualBlock2 = + this.snapshotBuilder.from(new Location<World>(world, this.sx + 1, y, this.bz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(topVisualBlock2.getLocation().get().createSnapshot(), topVisualBlock2)); + BlockSnapshot topVisualBlock3 = + this.snapshotBuilder.from(new Location<World>(world, this.bx - 1, y, this.bz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(topVisualBlock3.getLocation().get().createSnapshot(), topVisualBlock3)); + + if (STEP != 0) { + for (int x = this.sx + STEP; x < this.bx - STEP / 2; x += STEP) { + if ((y != 0 && x >= this.sx && x <= this.bx) || (x > this.minx && x < this.maxx)) { + BlockSnapshot visualBlock = + this.snapshotBuilder.from(new Location<World>(world, x, y, this.bz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(visualBlock.getLocation().get().createSnapshot(), visualBlock)); + } + } + } + } + + public void addBottomLine(World world, int y, BlockType cornerMaterial, BlockType accentMaterial) { + BlockSnapshot bottomVisualBlock1 = + this.snapshotBuilder.from(new Location<World>(world, this.sx + 1, y, this.sz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(bottomVisualBlock1.getLocation().get().createSnapshot(), bottomVisualBlock1)); + this.corners.add(bottomVisualBlock1.getPosition()); + BlockSnapshot bottomVisualBlock2 = + this.snapshotBuilder.from(new Location<World>(world, this.bx - 1, y, this.sz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(bottomVisualBlock2.getLocation().get().createSnapshot(), bottomVisualBlock2)); + + if (STEP != 0) { + for (int x = this.sx + STEP; x < this.bx - STEP / 2; x += STEP) { + if ((y != 0 && x >= this.sx && x <= this.bx) || (x > this.minx && x < this.maxx)) { + BlockSnapshot visualBlock = + this.snapshotBuilder.from(new Location<World>(world, x, y, this.sz)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(visualBlock.getLocation().get().createSnapshot(), visualBlock)); + } + } + } + } + + public void addLeftLine(World world, int y, BlockType cornerMaterial, BlockType accentMaterial) { + BlockSnapshot leftVisualBlock1 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.sz)).blockState(cornerMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(leftVisualBlock1.getLocation().get().createSnapshot(), leftVisualBlock1)); + this.corners.add(leftVisualBlock1.getPosition()); + BlockSnapshot leftVisualBlock2 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.sz + 1)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(leftVisualBlock2.getLocation().get().createSnapshot(), leftVisualBlock2)); + BlockSnapshot leftVisualBlock3 = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, this.bz - 1)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(leftVisualBlock3.getLocation().get().createSnapshot(), leftVisualBlock3)); + + if (STEP != 0) { + for (int z = this.sz + STEP; z < this.bz - STEP / 2; z += STEP) { + if ((y != 0 && z >= this.sz && z <= this.bz) || (z > this.minz && z < this.maxz)) { + BlockSnapshot visualBlock = + this.snapshotBuilder.from(new Location<World>(world, this.sx, y, z)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(visualBlock.getLocation().get().createSnapshot(), visualBlock)); + } + } + } + } + + public void addRightLine(World world, int y, BlockType cornerMaterial, BlockType accentMaterial) { + BlockSnapshot rightVisualBlock1 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.sz)).blockState(cornerMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(rightVisualBlock1.getLocation().get().createSnapshot(), rightVisualBlock1)); + this.corners.add(rightVisualBlock1.getPosition()); + BlockSnapshot rightVisualBlock2 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.sz + 1)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(rightVisualBlock2.getLocation().get().createSnapshot(), rightVisualBlock2)); + if (STEP != 0) { + for (int z = this.sz + STEP; z < this.bz - STEP / 2; z += STEP) { + if ((y != 0 && z >= this.sz && z <= this.bz) || (z > this.minz && z < this.maxz)) { + BlockSnapshot visualBlock = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, z)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(visualBlock.getLocation().get().createSnapshot(), visualBlock)); + } + } + } + BlockSnapshot rightVisualBlock3 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.bz - 1)).blockState(accentMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(rightVisualBlock3.getLocation().get().createSnapshot(), rightVisualBlock3)); + BlockSnapshot rightVisualBlock4 = + this.snapshotBuilder.from(new Location<World>(world, this.bx, y, this.bz)).blockState(cornerMaterial.getDefaultState()).build(); + this.newVisuals.add(new Transaction<BlockSnapshot>(rightVisualBlock4.getLocation().get().createSnapshot(), rightVisualBlock4)); + this.corners.add(rightVisualBlock4.getPosition()); + } + + public List<Transaction<BlockSnapshot>> getVisualTransactions() { + return this.visualTransactions; + } + + public List<Vector3i> getVisualCorners() { + return this.corners; + } + + private void removeElementsOutOfRange(List<Transaction<BlockSnapshot>> elements, int minx, int minz, int maxx, int maxz) { + for (int i = 0; i < elements.size(); i++) { + Location<World> location = elements.get(i).getFinal().getLocation().get(); + if (location.getX() < minx || location.getX() > maxx || location.getZ() < minz || location.getZ() > maxz) { + elements.remove(i); + } + } + } + + private Location<World> getVisibleLocation(World world, int x, int y, int z) { + Location<World> location = world.getLocation(x, y, z); + Direction direction = (isTransparent(location.getBlock())) ? Direction.DOWN : Direction.UP; + + while (location.getPosition().getY() >= 1 && + location.getPosition().getY() < world.getDimension().getBuildHeight() - 1 && + (!isTransparent(location.getRelative(Direction.UP).getBlock()) + || isTransparent(location.getBlock()))) { + location = location.getRelative(direction); + } + + return location; + } + + private boolean isTransparent(BlockState state) { + if (state.getType() == BlockTypes.SNOW_LAYER) { + return false; + } + + if (!this.isPlayerInWater && BlockUtil.getInstance().isBlockWater(state)) { + return false; + } + return NMSUtil.getInstance().isTransparent(state); + } + + public static ClaimVisual fromClaims(Set<Claim> claims, int height, Location<World> locality, GDPlayerData playerData, ClaimVisual visualization) { + if (visualization == null) { + visualization = new ClaimVisual(BASIC); + } + + for (Claim claim : claims) { + GDClaim gpClaim = (GDClaim) claim; + if (!gpClaim.children.isEmpty()) { + fromClaims(gpClaim.children, height, locality, playerData, visualization); + } + if (gpClaim.claimVisual != null && !gpClaim.claimVisual.visualTransactions.isEmpty()) { + visualization.visualTransactions.addAll(gpClaim.getVisualizer().visualTransactions); + } else { + visualization.createClaimBlockVisualWithType(gpClaim, height, locality, playerData, ClaimVisual.getClaimVisualType(gpClaim)); + } + } + + return visualization; + } + + private boolean useCuboidVisual() { + if (this.claim.cuboid) { + return true; + } + + final GDPlayerData ownerData = this.claim.getOwnerPlayerData(); + if (ownerData != null && (this.claim.getOwnerMinClaimLevel() > 0 || this.claim.getOwnerMaxClaimLevel() < 255)) { + return true; + } + // Claim could of been created with different min/max levels, so check Y values + if (this.claim.getLesserBoundaryCorner().getY() > 0 || this.claim.getGreaterBoundaryCorner().getY() < 255) { + return true; + } + + return false; + } +} diff --git a/sponge/src/main/java/com/griefdefender/internal/visual/GDClaimVisualType.java b/sponge/src/main/java/com/griefdefender/internal/visual/GDClaimVisualType.java new file mode 100644 index 0000000..629f030 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/internal/visual/GDClaimVisualType.java @@ -0,0 +1,155 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.internal.visual; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.CatalogType; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockType; + +public class GDClaimVisualType implements CatalogType { + + private final String id; + private final String name; + public BlockType visualAccentBlock; + public BlockType visualCornerBlock; + public BlockType visualFillerBlock; + + public GDClaimVisualType(String id, String name) { + this.id = id.toLowerCase(); + this.name = name.toLowerCase(); + } + + public BlockType getVisualAccentBlock() { + if (this.visualAccentBlock == null) { + this.initVisualBlocks(); + } + return this.visualAccentBlock; + } + + public BlockType getVisualCornerBlock() { + if (this.visualCornerBlock == null) { + this.initVisualBlocks(); + } + return this.visualCornerBlock; + } + + public BlockType getVisualFillerBlock() { + if (this.visualFillerBlock == null) { + this.initVisualBlocks(); + } + return this.visualFillerBlock; + } + + private void initVisualBlocks() { + if (this == ClaimVisual.ADMIN) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualAdminAccentBlock).orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:pumpkin").get(); + } + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualAdminCornerBlock).orElse(null); + if (this.visualCornerBlock == null) { + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:glowstone").get(); + } + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualAdminFillerBlock).orElse(null); + if (this.visualFillerBlock == null) { + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:pumpkin").get(); + } + } else if (this == ClaimVisual.BASIC) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualBasicAccentBlock).orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:gold_block").get(); + } + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualBasicCornerBlock).orElse(null); + if (this.visualCornerBlock == null) { + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:glowstone").get(); + } + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualBasicFillerBlock).orElse(null); + if (this.visualFillerBlock == null) { + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:gold_block").get(); + } + } else if (this == ClaimVisual.RESTORENATURE) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualNatureAccentBlock).orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:diamond_block").get(); + } + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualNatureCornerBlock).orElse(null); + if (this.visualCornerBlock == null) { + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:diamond_block").get(); + } + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:diamond_block").get(); + } else if (this == ClaimVisual.SUBDIVISION) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualSubdivisionAccentBlock).orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:white_wool").orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:wool").get(); + } + } + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualSubdivisionCornerBlock).orElse(null); + if (this.visualCornerBlock == null) { + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:iron_block").get(); + } + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualSubdivisionFillerBlock).orElse(null); + if (this.visualFillerBlock == null) { + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:white_wool").get(); + if (this.visualFillerBlock == null) { + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:wool").get(); + } + } + } else if (this == ClaimVisual.TOWN) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualTownAccentBlock).orElse(null); + if (this.visualAccentBlock == null) { + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:emerald_block").get(); + } + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualTownCornerBlock).orElse(null); + if (this.visualCornerBlock == null) { + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:glowstone").get(); + } + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, GriefDefenderPlugin.getGlobalConfig().getConfig().visual.visualTownFillerBlock).orElse(null); + if (this.visualFillerBlock == null) { + this.visualFillerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:emerald_block").get(); + } + } else { // DEFAULT + this.visualAccentBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:netherrack").get(); + this.visualCornerBlock = Sponge.getRegistry().getType(BlockType.class, "minecraft:redstone_ore").get(); + this.visualFillerBlock = this.visualAccentBlock; + } + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + public String toString() { + return this.name; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/listener/BlockEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/BlockEventHandler.java new file mode 100644 index 0000000..30c83af --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/BlockEventHandler.java @@ -0,0 +1,989 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.EventResultCache; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDFlags; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.BlockPosCache; +import com.griefdefender.util.CauseContextHelper; +import net.kyori.text.Component; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.block.tileentity.Piston; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.block.tileentity.carrier.Chest; +import org.spongepowered.api.data.Transaction; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.FallingBlock; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.block.ChangeBlockEvent; +import org.spongepowered.api.event.block.CollideBlockEvent; +import org.spongepowered.api.event.block.NotifyNeighborBlockEvent; +import org.spongepowered.api.event.block.tileentity.ChangeSignEvent; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.filter.cause.Root; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.util.Direction; +import org.spongepowered.api.world.LocatableBlock; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.explosion.Explosion; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +//event handlers related to blocks +public class BlockEventHandler { + + private int lastBlockPreTick = -1; + private boolean lastBlockPreCancelled = false; + + // convenience reference to singleton datastore + private final BaseStorage dataStore; + + // constructor + public BlockEventHandler(BaseStorage dataStore) { + this.dataStore = dataStore; + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onBlockPre(ChangeBlockEvent.Pre event) { + lastBlockPreTick = Sponge.getServer().getRunningTimeTicks(); + if (GriefDefenderPlugin.isSourceIdBlacklisted("block-pre", event.getSource(), event.getLocations().get(0).getExtent().getProperties())) { + return; + } + + final World world = event.getLocations().get(0).getExtent(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + + final Cause cause = event.getCause(); + final EventContext context = event.getContext(); + final User user = CauseContextHelper.getEventUser(event); + final boolean hasFakePlayer = context.containsKey(EventContextKeys.FAKE_PLAYER); + + if (user != null) { + if (context.containsKey(EventContextKeys.PISTON_RETRACT)) { + return; + } + } + + final LocatableBlock locatableBlock = cause.first(LocatableBlock.class).orElse(null); + final TileEntity tileEntity = cause.first(TileEntity.class).orElse(null); + Entity sourceEntity = null; + // Always use TE as source if available + final Object source = tileEntity != null ? tileEntity : cause.root(); + Location<World> sourceLocation = locatableBlock != null ? locatableBlock.getLocation() : tileEntity != null ? tileEntity.getLocation() : null; + if (sourceLocation == null && source instanceof Entity) { + // check entity + sourceEntity = ((Entity) source); + sourceLocation = sourceEntity.getLocation(); + } + final boolean pistonExtend = context.containsKey(EventContextKeys.PISTON_EXTEND); + final boolean isLiquidSource = context.containsKey(EventContextKeys.LIQUID_FLOW); + final boolean isFireSource = isLiquidSource ? false : context.containsKey(EventContextKeys.FIRE_SPREAD); + final boolean isLeafDecay = context.containsKey(EventContextKeys.LEAVES_DECAY); + if (!GDFlags.LEAF_DECAY && isLeafDecay) { + return; + } + if (!GDFlags.LIQUID_FLOW && isLiquidSource) { + return; + } + if (!GDFlags.BLOCK_SPREAD && isFireSource) { + return; + } + + lastBlockPreCancelled = false; + final boolean isForgePlayerBreak = context.containsKey(EventContextKeys.PLAYER_BREAK); + GDTimings.BLOCK_PRE_EVENT.startTimingIfSync(); + // Handle player block breaks separately + if (isForgePlayerBreak && !hasFakePlayer && source instanceof Player) { + final Player player = (Player) source; + GDClaim targetClaim = null; + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, player.getUniqueId()); + for (Location<World> location : event.getLocations()) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), location.getBlock(), world.getProperties())) { + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + if (location.getBlockType() == BlockTypes.AIR) { + continue; + } + if (!checkSurroundings(event, location, player, playerData, targetClaim)) { + event.setCancelled(true); + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + + // check overrides + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, source, location.getBlock(), player, TrustTypes.BUILDER, true); + if (result != Tristate.TRUE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD, + ImmutableMap.of( + "player", targetClaim.getOwnerName())); + GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, player, message); + event.setCancelled(true); + lastBlockPreCancelled = true; + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } + + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + + if (sourceLocation != null) { + GDPlayerData playerData = null; + if (user != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, user.getUniqueId()); + } + GDClaim sourceClaim = this.dataStore.getClaimAt(sourceLocation); + GDClaim targetClaim = null; + List<Location<World>> sourceLocations = event.getLocations(); + if (pistonExtend) { + // check next block in extend direction + sourceLocations = new ArrayList<>(event.getLocations()); + Location<World> location = sourceLocations.get(sourceLocations.size() - 1); + final Direction direction = locatableBlock.getLocation().getBlock().get(Keys.DIRECTION).get(); + final Location<World> dirLoc = location.getBlockRelative(direction); + sourceLocations.add(dirLoc); + } + for (Location<World> location : sourceLocations) { + // Mods such as enderstorage will send chest updates to itself + // We must ignore cases like these to avoid issues with mod + if (tileEntity != null) { + if (location.getPosition().equals(tileEntity.getLocation().getPosition())) { + continue; + } + } + + final BlockState blockState = location.getBlock(); + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + // If a player successfully interacted with a block recently such as a pressure plate, ignore check + // This fixes issues such as pistons not being able to extend + if (user != null && !isForgePlayerBreak && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-pre") == Tristate.TRUE) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, playerData.eventResultCache.lastTrust, Tristate.TRUE); + continue; + } + if (user != null && targetClaim.isUserTrusted(user, TrustTypes.BUILDER)) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, TrustTypes.BUILDER.getName().toLowerCase(), Tristate.TRUE); + continue; + } + if (sourceClaim.getOwnerUniqueId().equals(targetClaim.getOwnerUniqueId()) && user == null && sourceEntity == null && !isFireSource && !isLeafDecay) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, "owner", Tristate.TRUE); + continue; + } + if (user != null && pistonExtend) { + if (targetClaim.isUserTrusted(user, TrustTypes.ACCESSOR)) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, TrustTypes.ACCESSOR.getName().toLowerCase(), Tristate.TRUE); + continue; + } + } + if (isLeafDecay) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.LEAF_DECAY, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } else if (isFireSource) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_SPREAD, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } else if (isLiquidSource) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.LIQUID_FLOW, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + lastBlockPreCancelled = true; + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + continue; + } else if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, source, blockState, user) == Tristate.FALSE) { + // PRE events can be spammy so we need to avoid sending player messages here. + event.setCancelled(true); + lastBlockPreCancelled = true; + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } + } else if (user != null) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getPlayerData(world, user.getUniqueId()); + GDClaim targetClaim = null; + for (Location<World> location : event.getLocations()) { + // Mods such as enderstorage will send chest updates to itself + // We must ignore cases like these to avoid issues with mod + if (tileEntity != null) { + if (location.getPosition().equals(tileEntity.getLocation().getPosition())) { + continue; + } + } + + final BlockState blockState = location.getBlock(); + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + // If a player successfully interacted with a block recently such as a pressure plate, ignore check + // This fixes issues such as pistons not being able to extend + if (!isForgePlayerBreak && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-pre") == Tristate.TRUE) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, playerData.eventResultCache.lastTrust, Tristate.TRUE); + continue; + } + if (targetClaim.isUserTrusted(user, TrustTypes.BUILDER)) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, source, blockState, user, GDPermissions.BLOCK_BREAK, TrustTypes.BUILDER.getName().toLowerCase(), Tristate.TRUE); + continue; + } + + if (isFireSource) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_SPREAD, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } else if (isLiquidSource) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.LIQUID_FLOW, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + lastBlockPreCancelled = true; + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + continue; + } else if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, source, blockState, user) == Tristate.FALSE) { + event.setCancelled(true); + lastBlockPreCancelled = true; + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + return; + } + } + } + GDTimings.BLOCK_PRE_EVENT.stopTimingIfSync(); + } + + // Handle fluids flowing into claims + @Listener(order = Order.FIRST, beforeModifications = true) + public void onBlockNotify(NotifyNeighborBlockEvent event) { + LocatableBlock locatableBlock = event.getCause().first(LocatableBlock.class).orElse(null); + TileEntity tileEntity = event.getCause().first(TileEntity.class).orElse(null); + Location<World> sourceLocation = locatableBlock != null ? locatableBlock.getLocation() : tileEntity != null ? tileEntity.getLocation() : null; + GDClaim sourceClaim = null; + GDPlayerData playerData = null; + if (sourceLocation != null) { + if (GriefDefenderPlugin.isSourceIdBlacklisted("block-notify", event.getSource(), sourceLocation.getExtent().getProperties())) { + return; + } + } + + final User user = CauseContextHelper.getEventUser(event); + if (user == null) { + return; + } + if (sourceLocation == null) { + Player player = event.getCause().first(Player.class).orElse(null); + if (player == null) { + return; + } + + sourceLocation = player.getLocation(); + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); + } else { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(sourceLocation.getExtent(), user.getUniqueId()); + sourceClaim = this.dataStore.getClaimAt(sourceLocation, playerData.lastClaim.get()); + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(sourceLocation.getExtent().getUniqueId())) { + return; + } + + GDTimings.BLOCK_NOTIFY_EVENT.startTimingIfSync(); + Iterator<Direction> iterator = event.getNeighbors().keySet().iterator(); + GDClaim targetClaim = null; + while (iterator.hasNext()) { + Direction direction = iterator.next(); + Location<World> location = sourceLocation.getBlockRelative(direction); + Vector3i pos = location.getBlockPosition(); + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + if (sourceClaim.isWilderness() && targetClaim.isWilderness()) { + if (playerData != null) { + playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); + } + continue; + } else if (!sourceClaim.isWilderness() && targetClaim.isWilderness()) { + if (playerData != null) { + playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); + } + continue; + } else if (sourceClaim.getUniqueId().equals(targetClaim.getUniqueId())) { + if (playerData != null) { + playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE); + } + continue; + } else { + if (playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(targetClaim, "block-notify") == Tristate.TRUE) { + continue; + } + // Needed to handle levers notifying doors to open etc. + if (targetClaim.isUserTrusted(user, TrustTypes.ACCESSOR)) { + if (playerData != null) { + playerData.eventResultCache = new EventResultCache(targetClaim, "block-notify", Tristate.TRUE, TrustTypes.ACCESSOR.getName().toLowerCase()); + } + continue; + } + } + + // no claim crossing unless trusted + iterator.remove(); + } + GDTimings.BLOCK_NOTIFY_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onBlockCollide(CollideBlockEvent event, @Root Entity source) { + if (event instanceof CollideBlockEvent.Impact) { + return; + } + // ignore falling blocks + if (!GDFlags.COLLIDE_BLOCK || source instanceof FallingBlock) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.COLLIDE_BLOCK.getName(), source.getType().getId(), source.getWorld().getProperties())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.COLLIDE_BLOCK.getName(), event.getTargetBlock(), source.getWorld().getProperties())) { + return; + } + + final User user = CauseContextHelper.getEventUser(event); + if (user == null) { + return; + } + + GDTimings.BLOCK_COLLIDE_EVENT.startTimingIfSync(); + final BlockType blockType = event.getTargetBlock().getType(); + if (blockType.equals(BlockTypes.AIR) + || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetLocation().getExtent().getUniqueId())) { + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + + if (source instanceof Item && (blockType != BlockTypes.PORTAL && !NMSUtil.getInstance().isBlockPressurePlate(blockType))) { + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + + Vector3i collidePos = event.getTargetLocation().getBlockPosition(); + short shortPos = BlockUtil.getInstance().blockPosToShort(collidePos); + int entityId = NMSUtil.getInstance().getEntityMinecraftId(source); + BlockPosCache entityBlockCache = BlockUtil.ENTITY_BLOCK_CACHE.get(entityId); + if (entityBlockCache == null) { + entityBlockCache = new BlockPosCache(shortPos); + BlockUtil.ENTITY_BLOCK_CACHE.put(entityId, entityBlockCache); + } else { + Tristate result = entityBlockCache.getCacheResult(shortPos); + if (result != Tristate.UNDEFINED) { + if (result == Tristate.FALSE) { + event.setCancelled(true); + } + + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + } + + GDPlayerData playerData = null; + GDClaim targetClaim = null; + if (user instanceof Player) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(event.getTargetLocation().getExtent(), user.getUniqueId()); + targetClaim = this.dataStore.getClaimAtPlayer(playerData, event.getTargetLocation()); + } else { + targetClaim = this.dataStore.getClaimAt(event.getTargetLocation()); + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, event.getTargetLocation(), targetClaim, GDPermissions.COLLIDE_BLOCK, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true) == Tristate.TRUE) { + entityBlockCache.setLastResult(Tristate.TRUE); + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + if (GDFlags.PORTAL_USE && event.getTargetBlock().getType() == BlockTypes.PORTAL) { + if (GDPermissionManager.getInstance().getFinalPermission(event, event.getTargetLocation(), targetClaim, GDPermissions.PORTAL_USE, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true) == Tristate.TRUE) { + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + if (event.getCause().root() instanceof Player){ + if (event.getTargetLocation().getExtent().getProperties().getTotalTime() % 20 == 0L) { // log once a second to avoid spam + // Disable message temporarily + //GriefDefender.sendMessage((Player) user, TextMode.Err, Messages.NoPortalFromProtectedClaim, claim.getOwnerName()); + /*final Text message = GriefDefenderPlugin.getInstance().messageData.permissionProtectedPortal + .apply(ImmutableMap.of( + "owner", targetClaim.getOwnerName())).build();*/ + event.setCancelled(true); + entityBlockCache.setLastResult(Tristate.FALSE); + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + return; + } + } + } + + GDTimings.BLOCK_COLLIDE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onProjectileImpactBlock(CollideBlockEvent.Impact event) { + if (!GDFlags.PROJECTILE_IMPACT_BLOCK || !(event.getSource() instanceof Entity)) { + return; + } + + final Entity source = (Entity) event.getSource(); + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.PROJECTILE_IMPACT_BLOCK.getName(), source.getType().getId(), source.getWorld().getProperties())) { + return; + } + + final User user = CauseContextHelper.getEventUser(event); + if (user == null) { + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getImpactPoint().getExtent().getUniqueId())) { + return; + } + + GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.startTimingIfSync(); + Location<World> impactPoint = event.getImpactPoint(); + GDClaim targetClaim = null; + GDPlayerData playerData = null; + if (user instanceof Player) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(event.getTargetLocation().getExtent(), user.getUniqueId()); + targetClaim = this.dataStore.getClaimAtPlayer(playerData, impactPoint); + } else { + targetClaim = this.dataStore.getClaimAt(impactPoint); + } + + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, impactPoint, targetClaim, GDPermissions.PROJECTILE_IMPACT_BLOCK, source, event.getTargetBlock(), user, TrustTypes.ACCESSOR, true); + if (result == Tristate.FALSE) { + event.setCancelled(true); + GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.stopTimingIfSync(); + return; + } + + GDTimings.PROJECTILE_IMPACT_BLOCK_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onExplosionPre(ExplosionEvent.Pre event) { + final World world = event.getExplosion().getWorld(); + if (!GDFlags.EXPLOSION_BLOCK || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + Object source = event.getSource(); + final Explosion explosion = event.getExplosion(); + if (explosion.getSourceExplosive().isPresent()) { + source = explosion.getSourceExplosive().get(); + } else { + Entity exploder = event.getCause().first(Entity.class).orElse(null); + if (exploder != null) { + source = exploder; + } + } + + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_BLOCK.getName(), source, event.getExplosion().getWorld().getProperties())) { + return; + } + + GDTimings.EXPLOSION_PRE_EVENT.startTimingIfSync(); + final User user = CauseContextHelper.getEventUser(event); + final Location<World> location = event.getExplosion().getLocation(); + final GDClaim radiusClaim = NMSUtil.getInstance().createClaimFromCenter(location, event.getExplosion().getRadius()); + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(location.getExtent().getUniqueId()); + final Set<Claim> surroundingClaims = claimManager.findOverlappingClaims(radiusClaim); + if (surroundingClaims.size() == 0) { + return; + } + for (Claim claim : surroundingClaims) { + // Use any location for permission check + Location<World> targetLocation = new Location<>(location.getExtent(), claim.getLesserBoundaryCorner()); + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.EXPLOSION_BLOCK, source, targetLocation, user, true); + if (result == Tristate.FALSE) { + event.setCancelled(true); + break; + } + } + + GDTimings.EXPLOSION_PRE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onExplosionDetonate(ExplosionEvent.Detonate event) { + final World world = event.getExplosion().getWorld(); + if (!GDFlags.EXPLOSION_BLOCK || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + Object source = event.getSource(); + if (source instanceof Explosion) { + final Explosion explosion = (Explosion) source; + if (explosion.getSourceExplosive().isPresent()) { + source = explosion.getSourceExplosive().get(); + } else { + Entity exploder = event.getCause().first(Entity.class).orElse(null); + if (exploder != null) { + source = exploder; + } + } + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_BLOCK.getName(), source, event.getExplosion().getWorld().getProperties())) { + return; + } + + GDTimings.EXPLOSION_EVENT.startTimingIfSync(); + final User user = CauseContextHelper.getEventUser(event); + GDClaim targetClaim = null; + final List<Location<World>> filteredLocations = new ArrayList<>(); + for (Location<World> location : event.getAffectedLocations()) { + targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location, targetClaim); + /*if (location.getPosition().getY() > ((net.minecraft.world.World) world).getSeaLevel() && !GriefDefenderPlugin.getActiveConfig(world.getUniqueId()).getConfig().claim.explosionSurface) { + filteredLocations.add(location); + continue; + }*/ + + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.EXPLOSION_BLOCK, source, location.getBlock(), user, true); + + if (result == Tristate.FALSE) { + // Avoid lagging server from large explosions. + if (event.getAffectedLocations().size() > 100) { + event.setCancelled(true); + break; + } + filteredLocations.add(location); + } + } + // Workaround for SpongeForge bug + if (event.isCancelled()) { + event.getAffectedLocations().clear(); + } else if (!filteredLocations.isEmpty()) { + event.getAffectedLocations().removeAll(filteredLocations); + } + GDTimings.EXPLOSION_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onBlockBreak(ChangeBlockEvent.Break event) { + if (!GDFlags.BLOCK_BREAK || event instanceof ExplosionEvent) { + return; + } + + if (lastBlockPreTick == Sponge.getServer().getRunningTimeTicks()) { + event.setCancelled(lastBlockPreCancelled); + return; + } + + Object source = event.getSource(); + // Handled in Explosion listeners + if (source instanceof Explosion) { + return; + } + + // Pistons are handled in onBlockPre + if (source == BlockTypes.PISTON || source instanceof Piston) { + return; + } + + final World world = event.getTransactions().get(0).getFinal().getLocation().get().getExtent(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.BLOCK_BREAK.getName(), source, world.getProperties())) { + return; + } + + final Player player = source instanceof Player ? (Player) source : null; + final User user = player != null ? player : CauseContextHelper.getEventUser(event); + + // ignore falling blocks when there is no user + // avoids dupes with falling blocks such as Dragon Egg + if (user == null && source instanceof FallingBlock) { + return; + } + GDClaim sourceClaim = null; + LocatableBlock locatable = null; + if (source instanceof LocatableBlock) { + locatable = (LocatableBlock) source; + sourceClaim = this.dataStore.getClaimAt(locatable.getLocation()); + } else { + sourceClaim = this.getSourceClaim(event.getCause()); + } + if (sourceClaim == null) { + return; + } + + GDTimings.BLOCK_BREAK_EVENT.startTimingIfSync(); + List<Transaction<BlockSnapshot>> transactions = event.getTransactions(); + GDClaim targetClaim = null; + for (Transaction<BlockSnapshot> transaction : transactions) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), transaction.getOriginal(), world.getProperties())) { + continue; + } + + Location<World> location = transaction.getOriginal().getLocation().orElse(null); + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + if (location == null || transaction.getOriginal().getState().getType() == BlockTypes.AIR) { + continue; + } + + // check overrides + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, source, transaction.getOriginal(), user, TrustTypes.BUILDER, true); + if (result != Tristate.TRUE) { + if (player != null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD, + ImmutableMap.of("player", targetClaim.getOwnerName())); + GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, player, message); + } + + event.setCancelled(true); + GDTimings.BLOCK_BREAK_EVENT.stopTimingIfSync(); + return; + } + } + GDTimings.BLOCK_BREAK_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onBlockPlace(ChangeBlockEvent.Place event) { + final Object source = event.getSource(); + // Pistons are handled in onBlockPre + if (source instanceof Piston) { + return; + } + + final World world = event.getTransactions().get(0).getFinal().getLocation().get().getExtent(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.BLOCK_PLACE.getName(), event.getSource(), world.getProperties())) { + return; + } + + GDTimings.BLOCK_PLACE_EVENT.startTimingIfSync(); + GDClaim sourceClaim = null; + LocatableBlock locatable = null; + final User user = CauseContextHelper.getEventUser(event); + if (source instanceof LocatableBlock) { + locatable = (LocatableBlock) source; + if (user != null && user instanceof Player) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(locatable.getWorld(), user.getUniqueId()); + sourceClaim = this.dataStore.getClaimAt(locatable.getLocation(), playerData.lastClaim.get()); + } else { + sourceClaim = this.dataStore.getClaimAt(locatable.getLocation()); + } + } else { + sourceClaim = this.getSourceClaim(event.getCause()); + } + if (sourceClaim == null) { + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + Player player = user != null && user instanceof Player ? (Player) user : null; + GDPlayerData playerData = null; + if (user != null) { + playerData = this.dataStore.getOrCreatePlayerData(world, user.getUniqueId()); + } + + GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(world.getProperties()); + if (sourceClaim != null && !(source instanceof User) && playerData != null && playerData.eventResultCache != null && playerData.eventResultCache.checkEventResultCache(sourceClaim, Flags.BLOCK_PLACE.getName()) == Tristate.TRUE) { + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + GDClaim targetClaim = null; + for (Transaction<BlockSnapshot> transaction : event.getTransactions()) { + final BlockSnapshot block = transaction.getFinal(); + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_PLACE.getName(), block, world.getProperties())) { + continue; + } + + Location<World> location = block.getLocation().orElse(null); + if (location == null) { + continue; + } + + targetClaim = this.dataStore.getClaimAt(location, targetClaim); + if (!checkSurroundings(event, location, player, playerData, targetClaim)) { + event.setCancelled(true); + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + if (GDFlags.BLOCK_PLACE) { + // Allow blocks to grow within claims + if (user == null && sourceClaim != null && sourceClaim.getUniqueId().equals(targetClaim.getUniqueId())) { + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + // check overrides + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_PLACE, source, block, user, TrustTypes.BUILDER, true); + if (result != Tristate.TRUE) { + // TODO - make sure this doesn't spam + /*if (source instanceof Player) { + final Text message = GriefDefenderPlugin.getInstance().messageData.permissionBuild + .apply(ImmutableMap.of( + "player", Text.of(targetClaim.getOwnerName()) + )).build(); + GriefDefenderPlugin.sendClaimDenyMessage(targetClaim, (Player) source, message); + }*/ + event.setCancelled(true); + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + } + + if (!(source instanceof Player)) { + continue; + } + + if (targetClaim.isWilderness() && activeConfig.getConfig().claim.autoChestClaimBlockRadius > -1) { + TileEntity tileEntity = block.getLocation().get().getTileEntity().orElse(null); + if (tileEntity == null || !(tileEntity instanceof Chest)) { + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + final int minClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.MIN_LEVEL).intValue(); + final int maxClaimLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.MAX_LEVEL).intValue(); + if (block.getPosition().getY() < minClaimLevel || block.getPosition().getY() > maxClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_CHEST_OUTSIDE_LEVEL, + ImmutableMap.of( + "min-claim-level", minClaimLevel, + "max-claim-level", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + + int radius = activeConfig.getConfig().claim.autoChestClaimBlockRadius; + + if (playerData.getInternalClaims().size() == 0) { + if (activeConfig.getConfig().claim.autoChestClaimBlockRadius == 0) { + GDCauseStackManager.getInstance().pushCause(player); + final ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) + .bounds(block.getPosition(), block.getPosition()) + .cuboid(false) + .owner(player.getUniqueId()) + .sizeRestrictions(false) + .type(ClaimTypes.BASIC) + .world(block.getLocation().get().getExtent().getUniqueId()) + .build(); + GDCauseStackManager.getInstance().popCause(); + if (result.successful()) { + final Claim claim = result.getClaim().get(); + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + claimManager.addClaim(claim, true); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_CHEST_CONFIRMATION); + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + } else { + Vector3i lesserBoundary = new Vector3i( + block.getPosition().getX() - radius, + minClaimLevel, + block.getPosition().getZ() - radius); + Vector3i greaterBoundary = new Vector3i( + block.getPosition().getX() + radius, + maxClaimLevel, + block.getPosition().getZ() + radius); + + while (radius >= 0) { + GDCauseStackManager.getInstance().pushCause(player); + ClaimResult result = GriefDefender.getRegistry().createBuilder(Claim.Builder.class) + .bounds(lesserBoundary, greaterBoundary) + .cuboid(false) + .owner(player.getUniqueId()) + .sizeRestrictions(false) + .type(ClaimTypes.BASIC) + .world(block.getLocation().get().getExtent().getUniqueId()) + .build(); + GDCauseStackManager.getInstance().popCause(); + if (!result.successful()) { + radius--; + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_AUTOMATIC_NOTIFICATION); + GDClaim newClaim = this.dataStore.getClaimAt(block.getLocation().get()); + ClaimVisual visualization = new ClaimVisual(newClaim, ClaimVisual.BASIC); + visualization.createClaimBlockVisuals(block.getPosition().getY(), player.getLocation(), playerData); + visualization.apply(player); + + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + return; + } + } + } + } + + if (targetClaim.isWilderness() && player.hasPermission(GDPermissions.CLAIM_SHOW_TUTORIAL)) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.TUTORIAL_CLAIM_BASIC)); + } + } + } + + GDTimings.BLOCK_PLACE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onSignChanged(ChangeSignEvent event) { + final User user = CauseContextHelper.getEventUser(event); + if (user == null) { + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetTile().getLocation().getExtent().getUniqueId())) { + return; + } + + GDTimings.SIGN_CHANGE_EVENT.startTimingIfSync(); + Location<World> location = event.getTargetTile().getLocation(); + // Prevent users exploiting signs + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INTERACT_BLOCK_SECONDARY, user, location.getBlock(), user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + if (user instanceof Player) { + event.setCancelled(true); + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ACCESS, + ImmutableMap.of("player", claim.getOwnerName())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, (Player) user, message); + return; + } + } + + GDTimings.SIGN_CHANGE_EVENT.stopTimingIfSync(); + } + + public GDClaim getSourceClaim(Cause cause) { + BlockSnapshot blockSource = cause.first(BlockSnapshot.class).orElse(null); + LocatableBlock locatableBlock = null; + TileEntity tileEntitySource = null; + Entity entitySource = null; + if (blockSource == null) { + locatableBlock = cause.first(LocatableBlock.class).orElse(null); + if (locatableBlock == null) { + entitySource = cause.first(Entity.class).orElse(null); + } + if (locatableBlock == null && entitySource == null) { + tileEntitySource = cause.first(TileEntity.class).orElse(null); + } + } + + GDClaim sourceClaim = null; + if (blockSource != null) { + sourceClaim = this.dataStore.getClaimAt(blockSource.getLocation().get()); + } else if (locatableBlock != null) { + sourceClaim = this.dataStore.getClaimAt(locatableBlock.getLocation()); + } else if (tileEntitySource != null) { + sourceClaim = this.dataStore.getClaimAt(tileEntitySource.getLocation()); + } else if (entitySource != null) { + Entity entity = entitySource; + if (entity instanceof Player) { + Player player = (Player) entity; + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); + } else { + sourceClaim = this.dataStore.getClaimAt(entity.getLocation()); + } + } + + return sourceClaim; + } + + // TODO: Add configuration for distance between claims + private boolean checkSurroundings(org.spongepowered.api.event.Event event, Location<World> location, Player player, GDPlayerData playerData, GDClaim targetClaim) { + if (playerData == null) { + return true; + } + // Don't allow players to break blocks next to land they do not own + if (!playerData.canIgnoreClaim(targetClaim)) { + // check surrounding blocks for access + for (Direction direction : BlockUtil.CARDINAL_SET) { + Location<World> loc = location.getBlockRelative(direction); + if (!(loc.getTileEntity().isPresent())) { + continue; + } + final GDClaim claim = this.dataStore.getClaimAt(loc, targetClaim); + if (!claim.isWilderness() && !targetClaim.equals(claim)) { + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, loc, claim, GDPermissions.BLOCK_BREAK, player, loc.getBlock(), player, TrustTypes.BUILDER, true); + if (result != Tristate.TRUE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_BUILD_NEAR_CLAIM, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + return false; + } + } + } + } + return true; + } +} diff --git a/sponge/src/main/java/com/griefdefender/listener/CommonEntityEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/CommonEntityEventHandler.java new file mode 100644 index 0000000..fb61623 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/CommonEntityEventHandler.java @@ -0,0 +1,385 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import java.lang.ref.WeakReference; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.gamemode.GameMode; +import org.spongepowered.api.entity.living.player.gamemode.GameModes; +import org.spongepowered.api.event.entity.MoveEntityEvent; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.ChatType; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDBorderClaimEvent; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDFlags; +import com.griefdefender.provider.MCClansProvider; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.EntityUtils; +import com.griefdefender.util.SpongeUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import nl.riebie.mcclans.api.ClanPlayer; + +public class CommonEntityEventHandler { + + private static CommonEntityEventHandler instance; + + public static CommonEntityEventHandler getInstance() { + return instance; + } + + static { + instance = new CommonEntityEventHandler(); + } + + private final BaseStorage storage; + + public CommonEntityEventHandler() { + this.storage = GriefDefenderPlugin.getInstance().dataStore; + } + + public boolean onEntityMove(MoveEntityEvent event, Location<World> fromLocation, Location<World> toLocation, Entity targetEntity){ + if ((!GDFlags.ENTER_CLAIM && !GDFlags.EXIT_CLAIM) || fromLocation.getBlockPosition().equals(toLocation.getBlockPosition())) { + return true; + } + + World world = targetEntity.getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return true; + } + final boolean enterBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTER_CLAIM.getName(), targetEntity, world.getProperties()); + final boolean exitBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXIT_CLAIM.getName(), targetEntity, world.getProperties()); + if (enterBlacklisted && exitBlacklisted) { + return true; + } + + GDTimings.ENTITY_MOVE_EVENT.startTimingIfSync(); + Player player = null; + GDPermissionUser user = null; + if (targetEntity instanceof Player) { + player = (Player) targetEntity; + user = PermissionHolderCache.getInstance().getOrCreateUser(player); + } else { + final Entity controller = EntityUtils.getControllingPassenger(targetEntity); + if (controller != null && controller instanceof Player) { + player = (Player) controller; + } + user = PermissionHolderCache.getInstance().getOrCreateUser(targetEntity.getCreator().orElse(null)); + } + + if (user != null) { + if (user.getInternalPlayerData().teleportDelay > 0) { + if (!toLocation.getBlockPosition().equals(VecHelper.toVector3i(user.getInternalPlayerData().teleportSourceLocation))) { + user.getInternalPlayerData().teleportDelay = 0; + TextAdapter.sendComponent(player, MessageCache.getInstance().TELEPORT_MOVE_CANCEL); + } + } + } + + if (player == null && user == null) { + // Handle border event without player + GDClaim fromClaim = this.storage.getClaimAt(fromLocation); + GDClaim toClaim = this.storage.getClaimAt(toLocation); + if (fromClaim != toClaim) { + GDBorderClaimEvent gpEvent = new GDBorderClaimEvent(targetEntity, fromClaim, toClaim); + // enter + if (GDFlags.ENTER_CLAIM && !enterBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, toLocation, toClaim, GDPermissions.ENTER_CLAIM, targetEntity, targetEntity, user) == Tristate.FALSE) { + gpEvent.cancelled(true); + if (event != null) { + event.setCancelled(true); + } + } + + // exit + if (GDFlags.EXIT_CLAIM && !exitBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, fromLocation, fromClaim, GDPermissions.EXIT_CLAIM, targetEntity, targetEntity, user) == Tristate.FALSE) { + gpEvent.cancelled(true); + if (event != null) { + event.setCancelled(true); + } + } + + GriefDefender.getEventManager().post(gpEvent); + if (gpEvent.cancelled()) { + if (event != null) { + event.setCancelled(true); + } + if (!(targetEntity instanceof Player) && EntityUtils.getOwnerUniqueId(targetEntity) == null) { + targetEntity.remove(); + } + return false; + } + } + GDTimings.ENTITY_MOVE_EVENT.stopTimingIfSync(); + return true; + } + + GDClaim fromClaim = null; + GDClaim toClaim = this.storage.getClaimAt(toLocation); + if (user != null) { + fromClaim = this.storage.getClaimAtPlayer(user.getInternalPlayerData(), fromLocation); + } else { + fromClaim = this.storage.getClaimAt(fromLocation); + } + + if (GDFlags.ENTER_CLAIM && !enterBlacklisted && user != null && user.getInternalPlayerData().lastClaim != null) { + final GDClaim lastClaim = (GDClaim) user.getInternalPlayerData().lastClaim.get(); + if (lastClaim != null && lastClaim != fromClaim) { + if (GDPermissionManager.getInstance().getFinalPermission(event, toLocation, toClaim, GDPermissions.ENTER_CLAIM, targetEntity, targetEntity, player, TrustTypes.ACCESSOR, false) == Tristate.FALSE) { + Location<World> claimCorner = new Location<>(toClaim.getWorld(), toClaim.lesserBoundaryCorner.getX(), player.getLocation().getY(), toClaim.greaterBoundaryCorner.getZ()); + Location<World> safeLocation = Sponge.getGame().getTeleportHelper().getSafeLocation(claimCorner, 9, 9).orElse(player.getWorld().getSpawnLocation()); + if (event != null) { + event.setToTransform(player.getTransform().setLocation(safeLocation)); + } + return false; + } + } + } + if (fromClaim == toClaim) { + GDTimings.ENTITY_MOVE_EVENT.stopTimingIfSync(); + return true; + } + // MCClans tag support + Component enterClanTag = null; + Component exitClanTag = null; + MCClansProvider clanApiProvider = GriefDefenderPlugin.getInstance().clanApiProvider; + if (clanApiProvider != null) { + if ((fromClaim.isBasicClaim() || (fromClaim.isSubdivision() && !fromClaim.isAdminClaim()))) { + ClanPlayer clanPlayer = clanApiProvider.getClanService().getClanPlayer(fromClaim.getOwnerUniqueId()); + if (clanPlayer != null && clanPlayer.getClan() != null) { + exitClanTag = SpongeUtil.fromSpongeText(Text.of(clanPlayer.getClan().getTagColored(), " ")); + } + } + if ((toClaim.isBasicClaim() || (toClaim.isSubdivision() && !toClaim.isAdminClaim()))) { + ClanPlayer clanPlayer = clanApiProvider.getClanService().getClanPlayer(toClaim.getOwnerUniqueId()); + if (clanPlayer != null && clanPlayer.getClan() != null) { + enterClanTag = SpongeUtil.fromSpongeText(Text.of(clanPlayer.getClan().getTagColored(), " ")); + } + } + } + + GDBorderClaimEvent gpEvent = new GDBorderClaimEvent(targetEntity, fromClaim, toClaim); + if (user != null && toClaim.isUserTrusted(user, TrustTypes.ACCESSOR)) { + GriefDefender.getEventManager().post(gpEvent); + if (gpEvent.cancelled()) { + event.setCancelled(true); + if (!(targetEntity instanceof Player) && EntityUtils.getOwnerUniqueId(targetEntity) == null) { + targetEntity.remove(); + } + final Component cancelMessage = gpEvent.getMessage().orElse(null); + if (player != null && cancelMessage != null) { + TextAdapter.sendComponent(player, cancelMessage); + } + return false; + } else { + final boolean showGpPrefix = GriefDefenderPlugin.getGlobalConfig().getConfig().message.enterExitShowGdPrefix; + user.getInternalPlayerData().lastClaim = new WeakReference<>(toClaim); + TextComponent welcomeMessage = (TextComponent) gpEvent.getEnterMessage().orElse(null); + if (welcomeMessage != null && !welcomeMessage.equals(TextComponent.empty()) && !welcomeMessage.content().equals("")) { + ChatType chatType = gpEvent.getEnterMessageChatType(); + if (showGpPrefix) { + TextAdapter.sendComponent(player, TextComponent.builder("") + .append(enterClanTag != null ? enterClanTag : GriefDefenderPlugin.GD_TEXT) + .append(welcomeMessage).build(), SpongeUtil.getSpongeChatType(chatType)); + } else { + TextAdapter.sendComponent(player, enterClanTag != null ? enterClanTag : welcomeMessage, SpongeUtil.getSpongeChatType(chatType)); + } + } + + Component farewellMessage = gpEvent.getExitMessage().orElse(null); + if (farewellMessage != null && !farewellMessage.equals(Text.of())) { + ChatType chatType = gpEvent.getExitMessageChatType(); + if (showGpPrefix) { + TextAdapter.sendComponent(player, TextComponent.builder("") + .append(exitClanTag != null ? exitClanTag : GriefDefenderPlugin.GD_TEXT) + .append(farewellMessage) + .build(), SpongeUtil.getSpongeChatType(chatType)); + } else { + TextAdapter.sendComponent(player, exitClanTag != null ? exitClanTag : farewellMessage, SpongeUtil.getSpongeChatType(chatType)); + } + } + + if (toClaim.isInTown()) { + user.getInternalPlayerData().inTown = true; + } else { + user.getInternalPlayerData().inTown = false; + } + } + + GDTimings.ENTITY_MOVE_EVENT.stopTimingIfSync(); + return true; + } + + if (fromClaim != toClaim) { + boolean enterCancelled = false; + boolean exitCancelled = false; + // enter + if (GDFlags.ENTER_CLAIM && !enterBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, toLocation, toClaim, GDPermissions.ENTER_CLAIM, targetEntity, targetEntity, user) == Tristate.FALSE) { + enterCancelled = true; + gpEvent.cancelled(true); + } + + // exit + if (GDFlags.EXIT_CLAIM && !exitBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, fromLocation, fromClaim, GDPermissions.EXIT_CLAIM, targetEntity, targetEntity, user) == Tristate.FALSE) { + exitCancelled = true; + gpEvent.cancelled(true); + } + + GriefDefender.getEventManager().post(gpEvent); + if (gpEvent.cancelled()) { + final Component cancelMessage = gpEvent.getMessage().orElse(null); + if (exitCancelled) { + if (cancelMessage != null && player != null) { + GriefDefenderPlugin.sendClaimDenyMessage(fromClaim, player, MessageCache.getInstance().PERMISSION_CLAIM_EXIT); + } + } else if (enterCancelled) { + if (cancelMessage != null && player != null) { + GriefDefenderPlugin.sendClaimDenyMessage(toClaim, player, MessageCache.getInstance().PERMISSION_CLAIM_ENTER); + } + } + + if (cancelMessage != null && player != null) { + TextAdapter.sendComponent(player, cancelMessage); + } + + event.setCancelled(true); + if (!(targetEntity instanceof Player) && EntityUtils.getOwnerUniqueId(targetEntity) == null) { + targetEntity.remove(); + } + GDTimings.ENTITY_MOVE_EVENT.stopTimingIfSync(); + return false; + } + + if (user != null) { + final boolean showGpPrefix = GriefDefenderPlugin.getGlobalConfig().getConfig().message.enterExitShowGdPrefix; + user.getInternalPlayerData().lastClaim = new WeakReference<>(toClaim); + Component welcomeMessage = gpEvent.getEnterMessage().orElse(null); + if (welcomeMessage != null && !welcomeMessage.equals(TextComponent.empty())) { + ChatType chatType = gpEvent.getEnterMessageChatType(); + if (showGpPrefix) { + TextAdapter.sendComponent(player, TextComponent.builder("") + .append(enterClanTag != null ? enterClanTag : GriefDefenderPlugin.GD_TEXT) + .append(welcomeMessage) + .build(), SpongeUtil.getSpongeChatType(chatType)); + } else { + TextAdapter.sendComponent(player, enterClanTag != null ? enterClanTag : welcomeMessage, SpongeUtil.getSpongeChatType(chatType)); + } + } + + Component farewellMessage = gpEvent.getExitMessage().orElse(null); + if (farewellMessage != null && !farewellMessage.equals(Text.of())) { + ChatType chatType = gpEvent.getExitMessageChatType(); + if (showGpPrefix) { + TextAdapter.sendComponent(player, TextComponent.builder("") + .append(exitClanTag != null ? exitClanTag : GriefDefenderPlugin.GD_TEXT) + .append(farewellMessage) + .build(), SpongeUtil.getSpongeChatType(chatType)); + } else { + TextAdapter.sendComponent(player, exitClanTag != null ? exitClanTag : farewellMessage, SpongeUtil.getSpongeChatType(chatType)); + } + } + + if (toClaim.isInTown()) { + user.getInternalPlayerData().inTown = true; + } else { + user.getInternalPlayerData().inTown = false; + } + + checkPlayerFlight(player, user.getInternalPlayerData(), fromClaim, toClaim); + } + } + + GDTimings.ENTITY_MOVE_EVENT.stopTimingIfSync(); + return true; + } + + private void checkPlayerFlight(Player player, GDPlayerData playerData, GDClaim fromClaim, GDClaim toClaim) { + final GameMode gameMode = player.get(Keys.GAME_MODE).orElse(null); + if (gameMode == null || gameMode == GameModes.CREATIVE || gameMode == GameModes.SPECTATOR) { + return; + } + + if (fromClaim == toClaim || !player.get(Keys.IS_FLYING).get()) { + // only handle player-fly in enter/exit + return; + } + + final Boolean noFly = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Boolean.class), playerData.getSubject(), Options.PLAYER_DENY_FLIGHT, toClaim); + final boolean adminFly = player.hasPermission(GDPermissions.BYPASS_OPTION); + final boolean ownerFly = toClaim.isBasicClaim() ? player.hasPermission(GDPermissions.USER_OPTION_PERK_OWNER_FLY_BASIC) : toClaim.isTown() ? player.hasPermission(GDPermissions.USER_OPTION_PERK_OWNER_FLY_TOWN) : false; + if (player.getUniqueId().equals(toClaim.getOwnerUniqueId()) && ownerFly) { + return; + } + if (!adminFly && noFly) { + player.offer(Keys.CAN_FLY, false); + player.offer(Keys.IS_FLYING, false); + playerData.ignoreFallDamage = true; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().OPTION_PLAYER_DENY_FLIGHT); + } + } + + public void sendInteractEntityDenyMessage(ItemStack playerItem, Entity entity, GDClaim claim, Player player) { + if (entity instanceof Player || (claim.getData() != null && !claim.getData().allowDenyMessages())) { + return; + } + + final String entityId = entity.getType().getId().toLowerCase(); + if (playerItem == null || playerItem.isEmpty()) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ENTITY, ImmutableMap.of( + "player", claim.getOwnerName(), + "entity", entityId)); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ITEM_ENTITY, ImmutableMap.of( + "item", playerItem.getType().getId().toLowerCase(), + "entity", entityId)); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/listener/EntityEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/EntityEventHandler.java new file mode 100644 index 0000000..0a62ab1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/EntityEventHandler.java @@ -0,0 +1,899 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.google.common.collect.ImmutableMap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.ChatType; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDBorderClaimEvent; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.internal.util.VecHelper; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDFlags; +import com.griefdefender.provider.MCClansProvider; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.CauseContextHelper; +import com.griefdefender.util.EntityUtils; +import com.griefdefender.util.PlayerUtil; +import com.griefdefender.util.SpongeUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import nl.riebie.mcclans.api.ClanPlayer; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.command.source.ConsoleSource; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityTypes; +import org.spongepowered.api.entity.ExperienceOrb; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.entity.hanging.ItemFrame; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.entity.projectile.Projectile; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; +import org.spongepowered.api.event.cause.entity.damage.source.EntityDamageSource; +import org.spongepowered.api.event.cause.entity.damage.source.IndirectEntityDamageSource; +import org.spongepowered.api.event.cause.entity.teleport.TeleportType; +import org.spongepowered.api.event.cause.entity.teleport.TeleportTypes; +import org.spongepowered.api.event.entity.AttackEntityEvent; +import org.spongepowered.api.event.entity.CollideEntityEvent; +import org.spongepowered.api.event.entity.ConstructEntityEvent; +import org.spongepowered.api.event.entity.DamageEntityEvent; +import org.spongepowered.api.event.entity.DestructEntityEvent; +import org.spongepowered.api.event.entity.MoveEntityEvent; +import org.spongepowered.api.event.entity.RideEntityEvent; +import org.spongepowered.api.event.entity.SpawnEntityEvent; +import org.spongepowered.api.event.filter.cause.First; +import org.spongepowered.api.event.filter.cause.Root; +import org.spongepowered.api.event.item.inventory.DropItemEvent; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.item.ItemTypes; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.explosion.Explosion; +import org.spongepowered.api.world.storage.WorldProperties; + +import java.lang.ref.WeakReference; +import java.time.Instant; +import java.util.Iterator; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Predicate; + +//handles events related to entities +public class EntityEventHandler { + + private int lastConstructEntityTick = -1; + private boolean lastConstructEntityCancelled = false; + + // convenience reference for the singleton datastore + private final BaseStorage dataStore; + + public EntityEventHandler(BaseStorage dataStore) { + this.dataStore = dataStore; + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityExplosionPre(ExplosionEvent.Pre event) { + if (!GDFlags.EXPLOSION_ENTITY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_ENTITY.getName(), event.getSource(), event.getTargetWorld().getProperties())) { + return; + } + + GDTimings.ENTITY_EXPLOSION_PRE_EVENT.startTimingIfSync(); + Location<World> location = event.getExplosion().getLocation(); + GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); + User user = CauseContextHelper.getEventUser(event); + Object source = event.getSource(); + if (source instanceof Explosion) { + final Explosion explosion = (Explosion) source; + if (explosion.getSourceExplosive().isPresent()) { + source = explosion.getSourceExplosive().get(); + } else { + Entity exploder = event.getCause().first(Entity.class).orElse(null); + if (exploder != null) { + source = exploder; + } + } + } + + /*if (location.getPosition().getY() > ((net.minecraft.world.World) location.getExtent()).getSeaLevel() && !GriefDefenderPlugin.getActiveConfig(location.getExtent().getUniqueId()).getConfig().claim.explosionSurface) { + event.setCancelled(true); + return; + }*/ + + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.EXPLOSION_ENTITY, source, location.getBlock(), user, true); + + if(result == Tristate.FALSE) { + event.setCancelled(true); + GDTimings.ENTITY_EXPLOSION_PRE_EVENT.stopTimingIfSync(); + return; + } + + GDTimings.ENTITY_EXPLOSION_PRE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityExplosionDetonate(ExplosionEvent.Detonate event) { + if (!GDFlags.EXPLOSION_ENTITY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.EXPLOSION_ENTITY.getName(), event.getSource(), event.getTargetWorld().getProperties())) { + return; + } + + GDTimings.ENTITY_EXPLOSION_DETONATE_EVENT.startTimingIfSync(); + final User user = CauseContextHelper.getEventUser(event); + Iterator<Entity> iterator = event.getEntities().iterator(); + GDClaim targetClaim = null; + Object source = event.getSource(); + if (source instanceof Explosion) { + final Explosion explosion = (Explosion) source; + if (explosion.getSourceExplosive().isPresent()) { + source = explosion.getSourceExplosive().get(); + } else { + Entity exploder = event.getCause().first(Entity.class).orElse(null); + if (exploder != null) { + source = exploder; + } + } + } + + while (iterator.hasNext()) { + Entity entity = iterator.next(); + targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(entity.getLocation(), targetClaim); + if (GDPermissionManager.getInstance().getFinalPermission(event, entity.getLocation(), targetClaim, GDPermissions.ENTITY_DAMAGE, source, entity, user) == Tristate.FALSE) { + iterator.remove(); + } + } + GDTimings.ENTITY_EXPLOSION_DETONATE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityConstruct(ConstructEntityEvent.Pre event, @Root Object source) { + lastConstructEntityTick = Sponge.getServer().getRunningTimeTicks(); + if (true || source instanceof ConsoleSource || !GDFlags.ENTITY_SPAWN) { + return; + } + + final World world = event.getTransform().getExtent(); + final String entityTypeId = event.getTargetType().getId(); + if (entityTypeId.equals(EntityTypes.EXPERIENCE_ORB.getId())) { + return; + } + + final Location<World> location = event.getTransform().getLocation(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_SPAWN.getName(), source, world.getProperties())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_CHUNK_SPAWN.getName(), source, world.getProperties())) { + return; + } + + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ENTITY_SPAWN.getName(), entityTypeId, world.getProperties())) { + return; + } + + GDTimings.ENTITY_SPAWN_PRE_EVENT.startTimingIfSync(); + final User user = CauseContextHelper.getEventUser(event); + final GDClaim targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); + if (targetClaim.isUserTrusted(user, TrustTypes.BUILDER)) { + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + + String permission = GDPermissions.ENTITY_SPAWN; + if (event.getTargetType() == EntityTypes.ITEM) { + if (user == null) { + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + if (!GDFlags.ITEM_SPAWN) { + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_SPAWN.getName(), entityTypeId, world.getProperties())) { + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + + permission = GDPermissions.ITEM_SPAWN; + if (source instanceof BlockSnapshot) { + final BlockSnapshot block = (BlockSnapshot) source; + final Location<World> blockLocation = block.getLocation().orElse(null); + if (blockLocation != null) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), block, world.getProperties())) { + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, source, block, user, true); + if (result != Tristate.UNDEFINED) { + if (result == Tristate.TRUE) { + // Check if item drop is allowed + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, permission, source, entityTypeId, user, true) == Tristate.FALSE) { + event.setCancelled(true); + } + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + event.setCancelled(true); + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + return; + } + } + } + } + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, permission, source, entityTypeId, user, true) == Tristate.FALSE) { + event.setCancelled(true); + } + GDTimings.ENTITY_SPAWN_PRE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntitySpawn(SpawnEntityEvent event) { + Object source = event.getSource(); + if (source instanceof ConsoleSource || !GDFlags.ENTITY_SPAWN || event.getEntities().isEmpty()) { + return; + } + + // If root cause is damage source, look for target as that should be passed instead + // Ex. Entity dies and drops an item would be after EntityDamageSource + if (source instanceof DamageSource) { + final Object target = event.getCause().after(DamageSource.class).orElse(null); + if (target != null) { + source = target; + } + } + + final boolean isChunkSpawn = event instanceof SpawnEntityEvent.ChunkLoad; + if (isChunkSpawn && !GDFlags.ENTITY_CHUNK_SPAWN) { + return; + } + if (event instanceof DropItemEvent) { + if (!GDFlags.ITEM_DROP) { + return; + } + // only handle item spawns from non-living + if (source instanceof Living || NMSUtil.getInstance().containsContainerPlayer(event.getCause())) { + return; + } + } + + final World world = event.getEntities().get(0).getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_SPAWN.getName(), source, world.getProperties())) { + return; + } + if (isChunkSpawn && GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_CHUNK_SPAWN.getName(), source, world.getProperties())) { + return; + } + + GDTimings.ENTITY_SPAWN_EVENT.startTimingIfSync(); + final User user = CauseContextHelper.getEventUser(event); + final Object actualSource = source; + event.filterEntities(new Predicate<Entity>() { + GDClaim targetClaim = null; + + @Override + public boolean test(Entity entity) { + if (entity instanceof ExperienceOrb) { + return true; + } + + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ENTITY_SPAWN.getName(), entity, world.getProperties())) { + return true; + } + + targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(entity.getLocation(), targetClaim); + if (targetClaim == null) { + return true; + } + + String permission = GDPermissions.ENTITY_SPAWN; + if (isChunkSpawn) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ENTITY_CHUNK_SPAWN.getName(), entity, world.getProperties())) { + return true; + } + permission = GDPermissions.ENTITY_CHUNK_SPAWN; + } + + if (!isChunkSpawn && entity instanceof Item) { + if (user == null) { + return true; + } + if (!GDFlags.ITEM_SPAWN) { + return true; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_SPAWN.getName(), entity, world.getProperties())) { + return true; + } + permission = GDPermissions.ITEM_SPAWN; + if (actualSource instanceof BlockSnapshot) { + final BlockSnapshot block = (BlockSnapshot) actualSource; + final Location<World> location = block.getLocation().orElse(null); + if (location != null) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), block, world.getProperties())) { + return true; + } + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.BLOCK_BREAK, actualSource, block, user, TrustTypes.ACCESSOR, true); + if (result != Tristate.UNDEFINED) { + if (result == Tristate.TRUE) { + // Check if item drop is allowed + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, permission, actualSource, entity, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + return false; + } + return true; + } + return false; + } + } + } + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, entity.getLocation(), targetClaim, permission, actualSource, entity, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + return false; + } + + return true; + } + }); + + GDTimings.ENTITY_SPAWN_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityAttack(AttackEntityEvent event, @First DamageSource damageSource) { + GDTimings.ENTITY_ATTACK_EVENT.startTimingIfSync(); + if (protectEntity(event, event.getTargetEntity(), event.getCause(), damageSource)) { + event.setCancelled(true); + } + GDTimings.ENTITY_ATTACK_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityDamage(DamageEntityEvent event, @First DamageSource damageSource) { + GDTimings.ENTITY_DAMAGE_EVENT.startTimingIfSync(); + if (protectEntity(event, event.getTargetEntity(), event.getCause(), damageSource)) { + event.setCancelled(true); + } + event.getTargetEntity().setCreator(null); + GDTimings.ENTITY_DAMAGE_EVENT.stopTimingIfSync(); + } + + public boolean protectEntity(Event event, Entity targetEntity, Cause cause, DamageSource damageSource) { + if (!GDFlags.ENTITY_DAMAGE || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(targetEntity.getWorld().getUniqueId())) { + return false; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ENTITY_DAMAGE.getName(), targetEntity, targetEntity.getWorld().getProperties())) { + return false; + } + + User user = CauseContextHelper.getEventUser(event); + Player player = cause.first(Player.class).orElse(null); + Object source = damageSource; + EntityDamageSource entityDamageSource = null; + final TileEntity tileEntity = cause.first(TileEntity.class).orElse(null); + // TE takes priority over entity damage sources + if (tileEntity != null) { + source = tileEntity; + } else if (damageSource instanceof EntityDamageSource) { + entityDamageSource = (EntityDamageSource) damageSource; + source = entityDamageSource.getSource(); + if (entityDamageSource instanceof IndirectEntityDamageSource) { + final Entity indirectSource = ((IndirectEntityDamageSource) entityDamageSource).getIndirectSource(); + if (indirectSource != null) { + source = indirectSource; + } + } + if (source instanceof Player) { + if (user == null) { + user = (User) source; + } + if (player == null) { + player = (Player) source; + } + } + } + + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_DAMAGE.getName(), source, targetEntity.getWorld().getProperties())) { + return false; + } + + GDPlayerData playerData = null; + if (player != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(targetEntity.getWorld(), player.getUniqueId()); + } + + if (NMSUtil.getInstance().isEntityMonster(targetEntity)) { + return false; + } + + final GDClaim claim = this.dataStore.getClaimAt(targetEntity.getLocation(), playerData != null ? playerData.lastClaim.get() : null); + final TrustType trustType = TrustTypes.BUILDER; + if (GDPermissionManager.getInstance().getFinalPermission(event, targetEntity.getLocation(), claim, GDPermissions.ENTITY_DAMAGE, source, targetEntity, user, trustType, true) == Tristate.FALSE) { + return true; + } + + // allow trusted users to attack entities within claim + if (!(targetEntity instanceof Player) && claim.isUserTrusted(user, TrustTypes.ACCESSOR)) { + return false; + } + + // Protect owned entities anywhere in world + if (entityDamageSource != null && !NMSUtil.getInstance().isEntityMonster(targetEntity)) { + Tristate perm = Tristate.UNDEFINED; + // Ignore PvP checks for owned entities + if (!(source instanceof Player) && !(targetEntity instanceof Player)) { + if (source instanceof User) { + User sourceUser = (User) source; + perm = GDPermissionManager.getInstance().getFinalPermission(event, targetEntity.getLocation(), claim, GDPermissions.ENTITY_DAMAGE, source, targetEntity, sourceUser, trustType, true); + if (targetEntity instanceof Living && perm == Tristate.TRUE) { + return false; + } + Optional<UUID> creatorUuid = targetEntity.getCreator(); + if (creatorUuid.isPresent()) { + Optional<User> creator = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(creatorUuid.get()); + if (creator.isPresent() && !creator.get().getUniqueId().equals(sourceUser.getUniqueId())) { + return true; + } + } else if (sourceUser.getUniqueId().equals(claim.getOwnerUniqueId())) { + return true; + } + + return false; + } else { + if (targetEntity instanceof Player) { + if (NMSUtil.getInstance().isEntityMonster((Entity) source)) { + if (GDPermissionManager.getInstance().getFinalPermission(event, targetEntity.getLocation(), claim, GDPermissions.ENTITY_DAMAGE, source, targetEntity, user, trustType, true) != Tristate.TRUE) { + return true; + } + } + } else if (targetEntity instanceof Living && !NMSUtil.getInstance().isEntityMonster(targetEntity)) { + if (user != null && !user.getUniqueId().equals(claim.getOwnerUniqueId()) && perm != Tristate.TRUE) { + return true; + } + } + } + } + } + + if (entityDamageSource == null || tileEntity != null) { + return false; + } + + Player attacker = null; + Projectile projectile = null; + + if (source != null) { + if (source instanceof Player) { + attacker = (Player) source; + } else if (source instanceof Projectile) { + projectile = (Projectile) source; + if (projectile.getShooter() instanceof Player) { + attacker = (Player) projectile.getShooter(); + } + } + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, targetEntity.getLocation(), claim, GDPermissions.ENTITY_DAMAGE, attacker, targetEntity, user, trustType, true) == Tristate.FALSE) { + return true; + } + + return false; + } + + @Listener(order = Order.POST) + public void onEntityDamageMonitor(DamageEntityEvent event) { + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetEntity().getWorld().getUniqueId())) { + return; + } + + GDTimings.ENTITY_DAMAGE_MONITOR_EVENT.startTimingIfSync(); + //FEATURE: prevent players who very recently participated in pvp combat from hiding inventory to protect it from looting + //FEATURE: prevent players who are in pvp combat from logging out to avoid being defeated + + if (event.getTargetEntity().getType() != EntityTypes.PLAYER || NMSUtil.getInstance().isEntityMonster(event.getTargetEntity())) { + GDTimings.ENTITY_DAMAGE_MONITOR_EVENT.stopTimingIfSync(); + return; + } + + Player defender = (Player) event.getTargetEntity(); + + //only interested in entities damaging entities (ignoring environmental damage) + // the rest is only interested in entities damaging entities (ignoring environmental damage) + if (!(event.getCause().root() instanceof EntityDamageSource)) { + GDTimings.ENTITY_DAMAGE_MONITOR_EVENT.stopTimingIfSync(); + return; + } + + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(defender.getWorld(), defender.getUniqueId()); + GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, defender.getLocation()); + EntityDamageSource entityDamageSource = (EntityDamageSource) event.getCause().root(); + + //if not in a pvp rules world, do nothing + if (!claim.isPvpEnabled()) { + GDTimings.ENTITY_DAMAGE_MONITOR_EVENT.stopTimingIfSync(); + return; + } + + GDTimings.ENTITY_DAMAGE_MONITOR_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onDestructEntity(DestructEntityEvent event) { + // Thread.dumpStack(); + } + + // when an entity drops items on death + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityDropItemDeath(DropItemEvent.Destruct event) { + if (!GDFlags.ITEM_DROP || event.getEntities().isEmpty()) { + return; + } + + final World world = event.getEntities().get(0).getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + Object source = event.getSource(); + // If root cause is damage source, look for target as that should be passed instead + // Ex. Entity dies and drops an item would be after EntityDamageSource + if (source instanceof DamageSource) { + final Object target = event.getCause().after(DamageSource.class).orElse(null); + if (target != null) { + source = target; + } + } + if (!(source instanceof Entity)) { + return; + } + + final Entity entity = (Entity) source; + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ITEM_DROP.getName(), entity, world.getProperties())) { + return; + } + + GDTimings.ENTITY_DROP_ITEM_DEATH_EVENT.startTimingIfSync(); + + final User user = CauseContextHelper.getEventUser(event); + event.filterEntities(new Predicate<Entity>() { + GDClaim targetClaim = null; + + @Override + public boolean test(Entity item) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.getName(), item, world.getProperties())) { + return true; + } + + targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(item.getLocation(), targetClaim); + if (targetClaim == null) { + return true; + } + + if (user == null) { + return true; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.getName(), item, world.getProperties())) { + return true; + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, item.getLocation(), targetClaim, GDPermissions.ITEM_DROP, entity, item, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + return false; + } + return true; + } + }); + + GDTimings.ENTITY_DROP_ITEM_DEATH_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityMove(MoveEntityEvent event){ + CommonEntityEventHandler.getInstance().onEntityMove(event, event.getFromTransform().getLocation(), event.getToTransform().getLocation(), event.getTargetEntity()); + } + + // when a player teleports + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityTeleport(MoveEntityEvent.Teleport event) { + if (!GDFlags.ENTITY_TELEPORT_FROM && !GDFlags.ENTITY_TELEPORT_TO) { + return; + } + + final Entity entity = event.getTargetEntity(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(entity.getWorld().getUniqueId())) { + return; + } + final boolean teleportFromBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_TELEPORT_FROM.getName(), entity, entity.getWorld().getProperties()); + final boolean teleportToBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_TELEPORT_TO.getName(), entity, entity.getWorld().getProperties()); + final boolean portalUseBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.PORTAL_USE.getName(), entity, entity.getWorld().getProperties()); + if (teleportFromBlacklisted && teleportToBlacklisted && portalUseBlacklisted) { + return; + } + + GDTimings.ENTITY_TELEPORT_EVENT.startTimingIfSync(); + Player player = null; + GDPermissionUser user = null; + if (entity instanceof Player) { + player = (Player) entity; + user = PermissionHolderCache.getInstance().getOrCreateUser(player); + } else { + user = PermissionHolderCache.getInstance().getOrCreateUser(entity.getCreator().orElse(null)); + } + + if (user == null) { + GDTimings.ENTITY_TELEPORT_EVENT.stopTimingIfSync(); + return; + } + + final Cause cause = event.getCause(); + final EventContext context = cause.getContext(); + + final TeleportType type = context.get(EventContextKeys.TELEPORT_TYPE).orElse(TeleportTypes.ENTITY_TELEPORT); + final Location<World> sourceLocation = event.getFromTransform().getLocation(); + final Location<World> destination = event.getToTransform().getLocation(); + // Handle BorderClaimEvent + if (!CommonEntityEventHandler.getInstance().onEntityMove(event, sourceLocation, destination, player)) { + event.setCancelled(true); + GDTimings.ENTITY_TELEPORT_EVENT.stopTiming(); + return; + } + + GDClaim sourceClaim = null; + GDPlayerData playerData = null; + if (player != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); + } else { + sourceClaim = this.dataStore.getClaimAt(sourceLocation); + } + + if (sourceClaim != null) { + if (GDFlags.ENTITY_TELEPORT_FROM && !teleportFromBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, sourceLocation, sourceClaim, GDPermissions.ENTITY_TELEPORT_FROM, type, entity, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + boolean cancelled = true; + if (GDFlags.PORTAL_USE && type.equals(TeleportTypes.PORTAL)) { + if (portalUseBlacklisted || GDPermissionManager.getInstance().getFinalPermission(event, sourceLocation, sourceClaim, GDPermissions.PORTAL_USE, type, entity, user) == Tristate.TRUE) { + cancelled = false; + } + } + if (cancelled) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_PORTAL_EXIT, + ImmutableMap.of( + "player", sourceClaim.getOwnerName())); + if (player != null) { + GriefDefenderPlugin.sendMessage(player, message); + } + + event.setCancelled(true); + GDTimings.ENTITY_TELEPORT_EVENT.stopTimingIfSync(); + return; + } + } + } + + // check if destination world is enabled + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getToTransform().getExtent().getUniqueId())) { + GDTimings.ENTITY_TELEPORT_EVENT.stopTimingIfSync(); + return; + } + + final GDClaim toClaim = this.dataStore.getClaimAt(destination); + if (toClaim != null) { + if (GDFlags.ENTITY_TELEPORT_TO && !teleportToBlacklisted && GDPermissionManager.getInstance().getFinalPermission(event, destination, toClaim, GDPermissions.ENTITY_TELEPORT_TO, type, entity, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + boolean cancelled = true; + if (GDFlags.PORTAL_USE && type.equals(TeleportTypes.PORTAL)) { + if (portalUseBlacklisted || GDPermissionManager.getInstance().getFinalPermission(event, destination, toClaim, GDPermissions.PORTAL_USE, type, entity, user, TrustTypes.ACCESSOR, true) == Tristate.TRUE) { + cancelled = false; + } + } + if (cancelled) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_PORTAL_ENTER, + ImmutableMap.of( + "player", toClaim.getOwnerName())); + if (player != null) { + GriefDefenderPlugin.sendMessage(player, message); + } + + if (type.equals(EntityTypes.ENDER_PEARL)) { + player.getInventory().offer(ItemStack.of(ItemTypes.ENDER_PEARL, 1)); + } + event.setCancelled(true); + GDTimings.ENTITY_TELEPORT_EVENT.stopTimingIfSync(); + return; + } + } + } + + if (player != null && !sourceLocation.getExtent().getUniqueId().equals(destination.getExtent().getUniqueId())) { + // new world, check if player has world storage for it + GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(destination.getExtent().getUniqueId()); + + // update lastActive timestamps for claims this player owns + WorldProperties worldProperties = destination.getExtent().getProperties(); + UUID playerUniqueId = player.getUniqueId(); + for (Claim claim : this.dataStore.getClaimWorldManager(worldProperties.getUniqueId()).getWorldClaims()) { + if (claim.getOwnerUniqueId().equals(playerUniqueId)) { + // update lastActive timestamp for claim + claim.getData().setDateLastActive(Instant.now()); + claimWorldManager.addClaim(claim); + } else if (claim.getParent().isPresent() && claim.getParent().get().getOwnerUniqueId().equals(playerUniqueId)) { + // update lastActive timestamp for subdivisions if parent owner logs on + claim.getData().setDateLastActive(Instant.now()); + claimWorldManager.addClaim(claim); + } + } + } + + if (playerData != null) { + if (toClaim.isTown()) { + playerData.inTown = true; + } else { + playerData.inTown = false; + } + } + // TODO + /*if (event.getCause().first(PortalTeleportCause.class).isPresent()) { + // FEATURE: when players get trapped in a nether portal, send them back through to the other side + CheckForPortalTrapTask task = new CheckForPortalTrapTask(player, event.getFromTransform().getLocation()); + Sponge.getGame().getScheduler().createTaskBuilder().delayTicks(200).execute(task).submit(GriefDefender.instance); + }*/ + GDTimings.ENTITY_TELEPORT_EVENT.stopTimingIfSync(); + } + + // Protects Item Frames + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityCollideEntity(CollideEntityEvent event) { + if (!GDFlags.COLLIDE_ENTITY || event instanceof CollideEntityEvent.Impact) { + return; + } + //if (GriefDefenderPlugin.isSourceIdBlacklisted(ClaimFlag.ENTITY_COLLIDE_ENTITY.toString(), event.getSource(), event.getEntities().get(0).getWorld().getProperties())) { + // return; + //} + + Object rootCause = event.getCause().root(); + final boolean isRootEntityItemFrame = rootCause instanceof ItemFrame; + if (!isRootEntityItemFrame) { + return; + } + + GDTimings.ENTITY_COLLIDE_EVENT.startTimingIfSync(); + event.filterEntities(new Predicate<Entity>() { + @Override + public boolean test(Entity entity) { + // Avoid living entities breaking itemframes + if (isRootEntityItemFrame && entity instanceof Living) { + return false; + } + + return true; + } + }); + GDTimings.ENTITY_COLLIDE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onProjectileImpactEntity(CollideEntityEvent.Impact event) { + if (!GDFlags.PROJECTILE_IMPACT_ENTITY) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.PROJECTILE_IMPACT_ENTITY.getName(), event.getSource(), event.getImpactPoint().getExtent().getProperties())) { + return; + } + + final User user = CauseContextHelper.getEventUser(event); + if (user == null || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getImpactPoint().getExtent().getUniqueId())) { + return; + } + + GDTimings.PROJECTILE_IMPACT_ENTITY_EVENT.startTimingIfSync(); + Object source = event.getCause().root(); + Location<World> impactPoint = event.getImpactPoint(); + GDClaim targetClaim = null; + for (Entity entity : event.getEntities()) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.PROJECTILE_IMPACT_ENTITY.getName(), entity, event.getImpactPoint().getExtent().getProperties())) { + return; + } + targetClaim = this.dataStore.getClaimAt(impactPoint, targetClaim); + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, impactPoint, targetClaim, GDPermissions.PROJECTILE_IMPACT_ENTITY, source, entity, user, TrustTypes.ACCESSOR, true); + if (result == Tristate.FALSE) { + if (GDPermissionManager.getInstance().getFinalPermission(event, impactPoint, targetClaim, GDPermissions.PROJECTILE_IMPACT_ENTITY, source, entity, user) == Tristate.TRUE) { + GDTimings.PROJECTILE_IMPACT_ENTITY_EVENT.stopTimingIfSync(); + return; + } + + event.setCancelled(true); + } + } + GDTimings.PROJECTILE_IMPACT_ENTITY_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onEntityMount(RideEntityEvent event) { + if (!GDFlags.ENTITY_RIDING) { + return; + } + + final Entity entity = event.getTargetEntity(); + final World world = entity.getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isSourceIdBlacklisted(Flags.ENTITY_RIDING.getName(), entity, world.getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ENTITY_RIDING.getName(), entity, world.getUniqueId())) { + return; + } + + GDTimings.ENTITY_MOUNT_EVENT.startTiming(); + final Object source = event.getSource(); + Player player = source instanceof Player ? (Player) source : null; + final Location<World> location = entity.getLocation(); + final GDClaim targetClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); + + if (GDPermissionManager.getInstance().getFinalPermission(event, location, targetClaim, GDPermissions.ENTITY_RIDING, source, entity, player, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + if (player != null) { + //sendInteractEntityDenyMessage(targetClaim, player, null, entity); + } + event.setCancelled(true); + } + + GDTimings.ENTITY_MOUNT_EVENT.stopTiming(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/listener/LuckPermsEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/LuckPermsEventHandler.java new file mode 100644 index 0000000..4de9475 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/LuckPermsEventHandler.java @@ -0,0 +1,54 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.permission.GDPermissionHolder; +import me.lucko.luckperms.api.LuckPermsApi; +import me.lucko.luckperms.api.event.group.GroupDataRecalculateEvent; +import me.lucko.luckperms.api.event.user.UserDataRecalculateEvent; + +public class LuckPermsEventHandler { + + private final LuckPermsApi luckPermsApi; + + public LuckPermsEventHandler(LuckPermsApi luckPermsApi) { + this.luckPermsApi = luckPermsApi; + this.luckPermsApi.getEventBus().subscribe(GroupDataRecalculateEvent.class, this::onGroupDataRecalculate); + this.luckPermsApi.getEventBus().subscribe(UserDataRecalculateEvent.class, this::onUserDataRecalculate); + } + + public void onGroupDataRecalculate(GroupDataRecalculateEvent event) { + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateGroup(event.getGroup().getName()); + PermissionHolderCache.getInstance().getOrCreatePermissionCache(holder).invalidateAll(); + } + + public void onUserDataRecalculate(UserDataRecalculateEvent event) { + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateUser(event.getUser().getUuid()); + PermissionHolderCache.getInstance().getOrCreatePermissionCache(holder).invalidateAll(); + PermissionHolderCache.getInstance().getOrCreatePermissionCache(GriefDefenderPlugin.DEFAULT_HOLDER).invalidateAll(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/listener/MCClansEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/MCClansEventHandler.java new file mode 100644 index 0000000..fc489f2 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/MCClansEventHandler.java @@ -0,0 +1,79 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.claim.GDClaim; +import nl.riebie.mcclans.api.events.ClanCreateEvent; +import nl.riebie.mcclans.api.events.ClanSetHomeEvent; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.world.World; + +public class MCClansEventHandler { + + public void onClanSetHome(ClanSetHomeEvent event) { + final World world = event.getLocation().getExtent(); + if (!GriefDefenderPlugin.getGlobalConfig().getConfig().town.clanRequireTown || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(event.getLocation()); + if (!claim.isInTown()) { + event.setCancelledWithMessage("You must be in a town in order to set your clan home."); + return; + } + if (!claim.getOwnerUniqueId().equals(event.getClan().getOwner().getUUID())) { + event.setCancelledWithMessage("You do not own this town."); + return; + } + } + + public void onClanCreate(ClanCreateEvent event) { + if (!GriefDefenderPlugin.getGlobalConfig().getConfig().town.clanRequireTown) { + return; + } + + final Player player = Sponge.getServer().getPlayer(event.getOwner().getUUID()).orElse(null); + if (player == null) { + return; + } + + final World world = player.getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(world, player.getUniqueId()); + for (Claim claim : playerData.getInternalClaims()) { + if (claim.isTown()) { + return; + } + } + event.setCancelledWithMessage("You must own a town in order to create a clan."); + } +} diff --git a/sponge/src/main/java/com/griefdefender/listener/NucleusEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/NucleusEventHandler.java new file mode 100644 index 0000000..1917ce4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/NucleusEventHandler.java @@ -0,0 +1,59 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.SpongeUtil; +import io.github.nucleuspowered.nucleus.api.events.NucleusHomeEvent; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +public class NucleusEventHandler { + + private static final BaseStorage DATASTORE = GriefDefenderPlugin.getInstance().dataStore; + + @Listener + public void onSetHome(NucleusHomeEvent.Create event) { + Location<World> location = event.getLocation().orElse(null); + if (location == null) { + return; + } + + GDClaim claim = DATASTORE.getClaimAt(location); + if (claim != null && !claim.isWilderness() && !claim.isAdminClaim()) { + if (!claim.isUserTrusted(event.getUser(), TrustTypes.ACCESSOR)) { + event.setCancelled(true); + event.setCancelMessage(SpongeUtil.getSpongeText(TextComponent.of("You must be trusted in order to use /sethome here.").color(TextColor.RED))); + } + } + } + +} diff --git a/sponge/src/main/java/com/griefdefender/listener/PlayerEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/PlayerEventHandler.java new file mode 100644 index 0000000..2e49220 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/PlayerEventHandler.java @@ -0,0 +1,1696 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.flowpowered.math.vector.Vector3d; +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.command.CommandHelper; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.internal.provider.WorldEditProvider; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.GDFlags; +import com.griefdefender.provider.NucleusProvider; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.EconomyUtil; +import com.griefdefender.util.PaginationUtil; +import com.griefdefender.util.PlayerUtil; +import com.griefdefender.util.SpongeUtil; +import io.github.nucleuspowered.nucleus.api.chat.NucleusChatChannel; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.BlockTypes; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.command.CommandMapping; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.data.property.entity.EyeLocationProperty; +import org.spongepowered.api.data.type.HandType; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.living.ArmorStand; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.action.InteractEvent; +import org.spongepowered.api.event.block.InteractBlockEvent; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.command.SendCommandEvent; +import org.spongepowered.api.event.entity.DestructEntityEvent; +import org.spongepowered.api.event.entity.InteractEntityEvent; +import org.spongepowered.api.event.entity.living.humanoid.HandInteractEvent; +import org.spongepowered.api.event.entity.living.humanoid.player.RespawnPlayerEvent; +import org.spongepowered.api.event.filter.cause.First; +import org.spongepowered.api.event.filter.cause.Root; +import org.spongepowered.api.event.item.inventory.ChangeInventoryEvent; +import org.spongepowered.api.event.item.inventory.ClickInventoryEvent; +import org.spongepowered.api.event.item.inventory.DropItemEvent; +import org.spongepowered.api.event.item.inventory.InteractInventoryEvent; +import org.spongepowered.api.event.item.inventory.InteractItemEvent; +import org.spongepowered.api.event.item.inventory.UseItemStackEvent; +import org.spongepowered.api.event.message.MessageChannelEvent; +import org.spongepowered.api.event.network.ClientConnectionEvent; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.item.ItemTypes; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.item.inventory.ItemStackSnapshot; +import org.spongepowered.api.item.inventory.transaction.SlotTransaction; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.service.ban.BanService; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.channel.MessageChannel; +import org.spongepowered.api.text.channel.MessageReceiver; +import org.spongepowered.api.text.channel.type.FixedMessageChannel; +import org.spongepowered.api.text.format.TextColors; +import org.spongepowered.api.util.blockray.BlockRay; +import org.spongepowered.api.util.blockray.BlockRayHit; +import org.spongepowered.api.world.Chunk; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.storage.WorldProperties; +import org.spongepowered.common.SpongeImpl; +import org.spongepowered.common.SpongeImplHooks; +import org.spongepowered.common.item.inventory.custom.CustomInventory; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +public class PlayerEventHandler { + + private final BaseStorage dataStore; + private final WorldEditProvider worldEditProvider; + private final BanService banService; + private int lastInteractItemPrimaryTick = -1; + private int lastInteractItemSecondaryTick = -1; + private boolean lastInteractItemCancelled = false; + + public PlayerEventHandler(BaseStorage dataStore, GriefDefenderPlugin plugin) { + this.dataStore = dataStore; + this.worldEditProvider = GriefDefenderPlugin.getInstance().worldEditProvider; + this.banService = Sponge.getServiceManager().getRegistration(BanService.class).get().getProvider(); + } + + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerChat(MessageChannelEvent.Chat event, @First Player player) { + GDTimings.PLAYER_CHAT_EVENT.startTimingIfSync(); + GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GDTimings.PLAYER_CHAT_EVENT.stopTimingIfSync(); + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (playerData.inTown && playerData.townChat) { + final MessageChannel channel = event.getChannel().orElse(null); + if (GriefDefenderPlugin.getInstance().nucleusApiProvider != null && channel != null) { + if (channel instanceof NucleusChatChannel) { + return; + } + } + final GDClaim sourceClaim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (sourceClaim.isInTown()) { + playerData.inTown = true; + } else { + playerData.inTown = false; + } + final GDClaim sourceTown = sourceClaim.getTownClaim(); + final Component townTag = sourceTown.getTownData().getTownTag().orElse(null); + + Text header = event.getFormatter().getHeader().toText(); + Text body = event.getFormatter().getBody().toText(); + Text footer = event.getFormatter().getFooter().toText(); + Text townMessage = Text.of(TextColors.GREEN, body); + if (townTag != null) { + townMessage = Text.of(townTag, townMessage); + } + event.setMessage(townMessage); + Set<CommandSource> recipientsToRemove = new HashSet<>(); + Iterator<MessageReceiver> iterator = event.getChannel().get().getMembers().iterator(); + while (iterator.hasNext()) { + MessageReceiver receiver = iterator.next(); + if (receiver instanceof Player) { + Player recipient = (Player) receiver; + if (GriefDefenderPlugin.getInstance().nucleusApiProvider != null) { + if (NucleusProvider.getPrivateMessagingService().isPresent() && NucleusProvider.getPrivateMessagingService().get().isSocialSpy(recipient)) { + // always allow social spy users + continue; + } + } + + final GDPlayerData targetPlayerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(recipient.getWorld(), recipient.getUniqueId()); + if (!targetPlayerData.inTown) { + recipientsToRemove.add(recipient); + continue; + } + + final GDClaim targetClaim = this.dataStore.getClaimAtPlayer(targetPlayerData, recipient.getLocation()); + final GDClaim targetTown = targetClaim.getTownClaim(); + if (targetPlayerData.canIgnoreClaim(targetClaim)) { + continue; + } + if (sourceTown != null && (targetTown == null || !sourceTown.getUniqueId().equals(targetTown.getUniqueId()))) { + recipientsToRemove.add(recipient); + } + } + } + + if (!recipientsToRemove.isEmpty()) { + Set<MessageReceiver> newRecipients = Sets.newHashSet(event.getChannel().get().getMembers().iterator()); + newRecipients.removeAll(recipientsToRemove); + event.setChannel(new FixedMessageChannel(newRecipients)); + } + } + + GDTimings.PLAYER_CHAT_EVENT.stopTimingIfSync(); + } + + // when a player uses a slash command... + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerCommand(SendCommandEvent event, @First Player player) { + if (!GDFlags.COMMAND_EXECUTE && !GDFlags.COMMAND_EXECUTE_PVP) { + return; + } + final boolean commandExecuteSourceBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.COMMAND_EXECUTE.getName(),event.getSource(), player.getWorld().getProperties()); + final boolean commandExecutePvpSourceBlacklisted = GriefDefenderPlugin.isSourceIdBlacklisted(Flags.COMMAND_EXECUTE_PVP.getName(),event.getSource(), player.getWorld().getProperties()); + + GDTimings.PLAYER_COMMAND_EVENT.startTimingIfSync(); + String command = event.getCommand(); + String[] args = event.getArguments().split(" "); + String[] parts = command.split(":"); + String pluginId = null; + + if (parts.length > 1) { + pluginId = parts[0]; + command = parts[1]; + } + + String message = "/" + event.getCommand() + " " + event.getArguments(); + if (pluginId == null || !pluginId.equals("minecraft")) { + CommandMapping commandMapping = Sponge.getCommandManager().get(command).orElse(null); + PluginContainer pluginContainer = null; + if (commandMapping != null) { + pluginContainer = Sponge.getCommandManager().getOwner(commandMapping).orElse(null); + if (pluginContainer != null) { + pluginId = pluginContainer.getId(); + } + } + if (pluginId == null) { + pluginId = "minecraft"; + } + } + + PaginationUtil.getInstance().updateActiveCommand(player.getUniqueId(), command, event.getArguments()); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + // if requires access trust, check for permission + Location<World> location = player.getLocation(); + GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, location); + if (playerData.canIgnoreClaim(claim)) { + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + String commandPermission = pluginId + "." + command; + + // first check the args + String argument = ""; + for (String arg : args) { + argument = argument + "." + arg; + } + + if (GDFlags.COMMAND_EXECUTE && !commandExecuteSourceBlacklisted && !GriefDefenderPlugin.isTargetIdBlacklisted(Flags.COMMAND_EXECUTE.getName(), commandPermission + argument, player.getWorld().getProperties())) { + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, player.getLocation(), claim, GDPermissions.COMMAND_EXECUTE, event.getSource(), commandPermission + argument, player); + if (result == Tristate.TRUE) { + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + if (result == Tristate.FALSE) { + final Component denyMessage = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.COMMAND_BLOCKED, + ImmutableMap.of( + "command", command, + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, denyMessage); + event.setCancelled(true); + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + } + /*if (GDFlags.COMMAND_EXECUTE_PVP && !commandExecutePvpSourceBlacklisted && playerData != null && (playerData.inPvpCombat(player.getWorld())) && !GriefDefenderPlugin.isTargetIdBlacklisted(Flags.COMMAND_EXECUTE_PVP.getName(), commandPermission + argument, player.getWorld().getProperties())) { + final Tristate result = GPPermissionManager.getInstance().getFinalPermission(event, player.getLocation(), claim, GPPermissions.COMMAND_EXECUTE_PVP, event.getSource(), commandPermission + argument, player); + if (result == Tristate.TRUE) { + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + if (result == Tristate.FALSE) { + final Component denyMessage = GriefDefenderPlugin.getInstance().messageData.pvpCommandBanned + .apply(ImmutableMap.of( + "command", command)).build(); + GriefDefenderPlugin.sendMessage(event.getCause().first(Player.class).get(), denyMessage); + event.setCancelled(true); + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + return; + } + }*/ + + GDTimings.PLAYER_COMMAND_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerLogin(ClientConnectionEvent.Login event) { + GDTimings.PLAYER_LOGIN_EVENT.startTimingIfSync(); + User player = event.getTargetUser(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getToTransform().getExtent().getUniqueId())) { + GDTimings.PLAYER_LOGIN_EVENT.stopTimingIfSync(); + return; + } + + final WorldProperties worldProperties = event.getToTransform().getExtent().getProperties(); + final UUID playerUniqueId = player.getUniqueId(); + final GDClaimManager claimWorldManager = this.dataStore.getClaimWorldManager(worldProperties.getUniqueId()); + final Instant dateNow = Instant.now(); + for (Claim claim : claimWorldManager.getWorldClaims()) { + if (claim.getType() != ClaimTypes.ADMIN && claim.getOwnerUniqueId().equals(playerUniqueId)) { + claim.getData().setDateLastActive(dateNow); + for (Claim subdivision : ((GDClaim) claim).children) { + subdivision.getData().setDateLastActive(dateNow); + } + ((GDClaim) claim).getInternalClaimData().setRequiresSave(true); + } + } + GDTimings.PLAYER_LOGIN_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST) + public void onPlayerJoin(ClientConnectionEvent.Join event) { + GDTimings.PLAYER_JOIN_EVENT.startTimingIfSync(); + Player player = event.getTargetEntity(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + GDTimings.PLAYER_JOIN_EVENT.stopTimingIfSync(); + return; + } + + UUID playerID = player.getUniqueId(); + + final GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), playerID); + final GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, player.getLocation()); + if (claim.isInTown()) { + playerData.inTown = true; + } + + GDTimings.PLAYER_JOIN_EVENT.stopTimingIfSync(); + } + + @Listener(order= Order.LAST) + public void onPlayerQuit(ClientConnectionEvent.Disconnect event) { + final Player player = event.getTargetEntity(); + if (!SpongeImpl.getServer().isServerRunning() || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + GDTimings.PLAYER_QUIT_EVENT.startTimingIfSync(); + UUID playerID = player.getUniqueId(); + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), playerID); + + if (this.worldEditProvider != null) { + this.worldEditProvider.revertVisuals(player, playerData, null); + this.worldEditProvider.removePlayer(player); + } + + playerData.onDisconnect(); + PaginationUtil.getInstance().removeActivePageData(player.getUniqueId()); + if (playerData.getClaims().isEmpty()) { + this.dataStore.clearCachedPlayerData(player.getWorld().getUniqueId(), playerID); + } + + GDTimings.PLAYER_QUIT_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerDeath(DestructEntityEvent.Death event) { + if (!(event.getTargetEntity() instanceof Player)) { + return; + } + + final Player player = (Player) event.getTargetEntity(); + GDCauseStackManager.getInstance().pushCause(player); + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final Tristate keepInventory = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Tristate.class), playerData.getSubject(), Options.PLAYER_KEEP_INVENTORY, claim); + //final Tristate keepLevel = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Tristate.class), playerData.getSubject(), Options.PLAYER_KEEP_LEVEL, claim); + if (keepInventory != Tristate.UNDEFINED) { + event.setKeepInventory(keepInventory.asBoolean()); + } + //if (keepLevel != Tristate.UNDEFINED) { + // event.setKeepLevel(keepLevel.asBoolean()); + // } + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerRespawn(RespawnPlayerEvent event) { + final World world = event.getToTransform().getExtent(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(event.getTargetEntity().getWorld(), event.getTargetEntity().getUniqueId()); + playerData.lastPvpTimestamp = null; + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerDispenseItem(DropItemEvent.Dispense event, @Root Entity spawncause) { + if (!GDFlags.ITEM_DROP || !(spawncause instanceof User)) { + return; + } + + final User user = (User) spawncause; + final World world = spawncause.getWorld(); + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return; + } + + GDTimings.PLAYER_DISPENSE_ITEM_EVENT.startTimingIfSync(); + Player player = user instanceof Player ? (Player) user : null; + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(world, user.getUniqueId()); + + for (Entity entityItem : event.getEntities()) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.toString(), entityItem, world.getProperties())) { + continue; + } + + Location<World> location = entityItem.getLocation(); + GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, location); + if (claim != null) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_DROP, user, entityItem, user, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + event.setCancelled(true); + if (spawncause instanceof Player) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ITEM_DROP, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", entityItem.getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } + GDTimings.PLAYER_DISPENSE_ITEM_EVENT.stopTimingIfSync(); + return; + } + } + } + GDTimings.PLAYER_DISPENSE_ITEM_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractInventoryOpen(InteractInventoryEvent.Open event, @First Player player) { + if (!GDFlags.INTERACT_INVENTORY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + final Cause cause = event.getCause(); + final EventContext context = cause.getContext(); + final BlockSnapshot blockSnapshot = context.get(EventContextKeys.BLOCK_HIT).orElse(BlockSnapshot.NONE); + if (blockSnapshot == BlockSnapshot.NONE) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_INVENTORY.getName(), blockSnapshot, player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_INVENTORY_OPEN_EVENT.startTimingIfSync(); + final Location<World> location = blockSnapshot.getLocation().get(); + final GDClaim claim = this.dataStore.getClaimAt(location); + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INVENTORY_OPEN, player, blockSnapshot, player, TrustTypes.CONTAINER, true); + if (result == Tristate.FALSE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INVENTORY_OPEN, + ImmutableMap.of( + "player", claim.getOwnerName(), + "block", blockSnapshot.getState().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + NMSUtil.getInstance().closePlayerScreen(player); + event.setCancelled(true); + } + + GDTimings.PLAYER_INTERACT_INVENTORY_OPEN_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractInventoryClose(InteractInventoryEvent.Close event, @Root Player player) { + final ItemStackSnapshot cursor = event.getCursorTransaction().getOriginal(); + if (cursor == ItemStackSnapshot.NONE || !GDFlags.ITEM_DROP || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.getName(), cursor, player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_INVENTORY_CLOSE_EVENT.startTimingIfSync(); + final Location<World> location = player.getLocation(); + final GDClaim claim = this.dataStore.getClaimAt(location); + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_DROP, player, cursor, player, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ITEM_DROP, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", cursor.getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + event.setCancelled(true); + } + + GDTimings.PLAYER_INTERACT_INVENTORY_CLOSE_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractInventoryClick(ClickInventoryEvent event, @First Player player) { + if (!GDFlags.INTERACT_INVENTORY_CLICK || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + if (NMSUtil.getInstance().isContainerCustomInventory(event.getTargetInventory())) { + } + + GDTimings.PLAYER_INTERACT_INVENTORY_CLICK_EVENT.startTimingIfSync(); + final Location<World> location = player.getLocation(); + final GDClaim claim = this.dataStore.getClaimAt(location); + final boolean isDrop = event instanceof ClickInventoryEvent.Drop; + final ItemStackSnapshot cursorItem = event.getCursorTransaction().getOriginal(); + // check if original cursor item can be dropped + if (isDrop && cursorItem != ItemStackSnapshot.NONE && !GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.getName(), cursorItem, player.getWorld().getProperties())) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_DROP, player, cursorItem, player, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ITEM_DROP, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", cursorItem.getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + event.setCancelled(true); + GDTimings.PLAYER_INTERACT_INVENTORY_CLICK_EVENT.stopTimingIfSync(); + return; + } + } + for (SlotTransaction transaction : event.getTransactions()) { + if (transaction.getOriginal() == ItemStackSnapshot.NONE) { + continue; + } + + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_INVENTORY_CLICK.getName(), transaction.getOriginal(), player.getWorld().getProperties())) { + continue; + } + + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INVENTORY_CLICK, player, transaction.getOriginal(), player, TrustTypes.CONTAINER, true); + if (result == Tristate.FALSE) { + Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ITEM, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", transaction.getOriginal().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + event.setCancelled(true); + GDTimings.PLAYER_INTERACT_INVENTORY_CLICK_EVENT.stopTimingIfSync(); + return; + } + + if (isDrop && transaction.getFinal() != ItemStackSnapshot.NONE) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_DROP.getName(), transaction.getFinal(), player.getWorld().getProperties())) { + continue; + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_DROP, player, transaction.getFinal(), player, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ITEM_DROP, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", transaction.getFinal().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + event.setCancelled(true); + GDTimings.PLAYER_INTERACT_INVENTORY_CLICK_EVENT.stopTimingIfSync(); + return; + } + } + } + GDTimings.PLAYER_INTERACT_INVENTORY_CLICK_EVENT.stopTimingIfSync(); + } + + // when a player interacts with an entity... + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractEntity(InteractEntityEvent.Primary event, @First Player player) { + if (!GDFlags.INTERACT_ENTITY_PRIMARY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + final Entity targetEntity = event.getTargetEntity(); + final HandType handType = event.getHandType(); + final ItemStack itemInHand = player.getItemInHand(handType).orElse(ItemStack.empty()); + final Object source = player; + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_ENTITY_PRIMARY.getName(), targetEntity, player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_ENTITY_PRIMARY_EVENT.startTimingIfSync(); + Location<World> location = targetEntity.getLocation(); + GDClaim claim = this.dataStore.getClaimAt(location); + if (event.isCancelled() && claim.getData().getPvpOverride() == Tristate.TRUE && targetEntity instanceof Player) { + event.setCancelled(false); + } + + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INTERACT_ENTITY_PRIMARY, source, targetEntity, player, TrustTypes.ACCESSOR, true); + if (result == Tristate.FALSE) { + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ENTITY_DAMAGE, source, targetEntity, player, TrustTypes.ACCESSOR, true) != Tristate.TRUE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_PROTECTED_ENTITY, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + event.setCancelled(true); + this.sendInteractEntityDenyMessage(itemInHand, targetEntity, claim, player, handType); + GDTimings.PLAYER_INTERACT_ENTITY_PRIMARY_EVENT.stopTimingIfSync(); + return; + } + } + } + + // when a player interacts with an entity... + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractEntity(InteractEntityEvent.Secondary event, @First Player player) { + if (!GDFlags.INTERACT_ENTITY_SECONDARY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + final Entity targetEntity = event.getTargetEntity(); + final HandType handType = event.getHandType(); + final ItemStack itemInHand = player.getItemInHand(handType).orElse(ItemStack.empty()); + final Object source = player; + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_ENTITY_SECONDARY.getName(), targetEntity, player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_ENTITY_SECONDARY_EVENT.startTimingIfSync(); + Location<World> location = targetEntity.getLocation(); + GDClaim claim = this.dataStore.getClaimAt(location); + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + + if (playerData.canIgnoreClaim(claim)) { + GDTimings.PLAYER_INTERACT_ENTITY_SECONDARY_EVENT.stopTimingIfSync(); + return; + } + + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INTERACT_ENTITY_SECONDARY, source, targetEntity, player, TrustTypes.ACCESSOR, true); + if (result == Tristate.TRUE && targetEntity instanceof ArmorStand) { + result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INVENTORY_OPEN, source, targetEntity, player, TrustTypes.CONTAINER, false); + } + if (result == Tristate.FALSE) { + event.setCancelled(true); + this.sendInteractEntityDenyMessage(itemInHand, targetEntity, claim, player, handType); + GDTimings.PLAYER_INTERACT_ENTITY_SECONDARY_EVENT.stopTimingIfSync(); + } + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractItem(InteractItemEvent event, @Root Player player) { + if (event instanceof InteractItemEvent.Primary) { + lastInteractItemPrimaryTick = Sponge.getServer().getRunningTimeTicks(); + } else { + lastInteractItemSecondaryTick = Sponge.getServer().getRunningTimeTicks(); + } + + final World world = player.getWorld(); + final HandInteractEvent handEvent = (HandInteractEvent) event; + final ItemStack itemInHand = player.getItemInHand(handEvent.getHandType()).orElse(ItemStack.empty()); + + handleItemInteract(event, player, world, itemInHand); + } + + // when a player picks up an item... + @Listener(order = Order.LAST, beforeModifications = true) + public void onPlayerPickupItem(ChangeInventoryEvent.Pickup.Pre event, @Root Player player) { + if (!GDFlags.ITEM_PICKUP || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_PICKUP.getName(), event.getTargetEntity(), player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_PICKUP_ITEM_EVENT.startTimingIfSync(); + final World world = player.getWorld(); + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(world, player.getUniqueId()); + Location<World> location = player.getLocation(); + GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, location); + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_PICKUP, player, event.getTargetEntity(), player, true) == Tristate.FALSE) { + event.setCancelled(true); + } + + GDTimings.PLAYER_PICKUP_ITEM_EVENT.stopTimingIfSync(); + } + + // when a player switches in-hand items + @Listener + public void onPlayerChangeHeldItem(ChangeInventoryEvent.Held event, @First Player player) { + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + + GDTimings.PLAYER_CHANGE_HELD_ITEM_EVENT.startTimingIfSync(); + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + + int count = 0; + // if he's switching to the golden shovel + for (SlotTransaction transaction : event.getTransactions()) { + ItemStackSnapshot newItemStack = transaction.getFinal(); + if (count == 1 && newItemStack != null && newItemStack.getType().equals(GriefDefenderPlugin.getInstance().modificationTool.getType())) { + playerData.lastShovelLocation = null; + playerData.endShovelLocation = null; + playerData.claimResizing = null; + // always reset to basic claims mode + if (playerData.shovelMode != ShovelTypes.BASIC) { + playerData.shovelMode = ShovelTypes.BASIC; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().MODE_BASIC); + } + + // tell him how many claim blocks he has available + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PLAYER_REMAINING_BLOCKS_3D, + ImmutableMap.of( + "block-amount", playerData.getRemainingClaimBlocks(), + "chunk-amount", playerData.getRemainingChunks())); + GriefDefenderPlugin.sendMessage(player, message); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PLAYER_REMAINING_BLOCKS_2D, + ImmutableMap.of( + "block-amount", playerData.getRemainingClaimBlocks())); + GriefDefenderPlugin.sendMessage(player, message); + } + + } + count++; + } + GDTimings.PLAYER_CHANGE_HELD_ITEM_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerUseItem(UseItemStackEvent.Start event, @First Player player) { + if (!GDFlags.ITEM_USE || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.ITEM_USE.getName(), event.getItemStackInUse().getType(), player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_USE_ITEM_EVENT.startTimingIfSync(); + Location<World> location = player.getLocation(); + GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(location.getExtent(), player.getUniqueId()); + GDClaim claim = this.dataStore.getClaimAtPlayer(playerData, location); + + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.ITEM_USE, player, event.getItemStackInUse().getType(), player, TrustTypes.ACCESSOR, true); + if (result == Tristate.FALSE) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_ITEM_USE, + ImmutableMap.of( + "item", event.getItemStackInUse().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + event.setCancelled(true); + } + GDTimings.PLAYER_USE_ITEM_EVENT.stopTimingIfSync(); + } + + @Listener + public void onInteractBlock(InteractBlockEvent event) { + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractBlockPrimary(InteractBlockEvent.Primary.MainHand event, @First Player player) { + final GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final HandType handType = event.getHandType(); + final ItemStack itemInHand = player.getItemInHand(handType).orElse(ItemStack.empty()); + if (event.getTargetBlock() != BlockSnapshot.NONE) { + // Run our item hook since Sponge no longer fires InteractItemEvent when targetting a non-air block + if (handleItemInteract(event, player, player.getWorld(), itemInHand).isCancelled()) { + return; + } + } else if (playerData.claimMode) { + if (investigateClaim(event, player, event.getTargetBlock(), itemInHand)) { + event.setCancelled(true); + } + } + + if (playerData.claimMode) { + return; + } + // check give pet + if (playerData.petRecipientUniqueId != null) { + playerData.petRecipientUniqueId = null; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().COMMAND_PET_TRANSFER_CANCEL); + event.setCancelled(true); + return; + } + if (!GDFlags.INTERACT_BLOCK_PRIMARY || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_BLOCK_PRIMARY.getName(), event.getTargetBlock().getState(), player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.startTimingIfSync(); + final BlockSnapshot clickedBlock = event.getTargetBlock(); + final Location<World> location = clickedBlock.getLocation().orElse(null); + final Object source = player; + if (location == null) { + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.stopTimingIfSync(); + return; + } + + final GDClaim claim = this.dataStore.getClaimAt(location); + final Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INTERACT_BLOCK_PRIMARY, source, clickedBlock.getState(), player, TrustTypes.BUILDER, true); + if (result == Tristate.FALSE) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_BREAK.getName(), clickedBlock.getState(), player.getWorld().getProperties())) { + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.stopTimingIfSync(); + return; + } + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.BLOCK_BREAK, source, clickedBlock.getState(), player, TrustTypes.BUILDER, true) == Tristate.TRUE) { + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.stopTimingIfSync(); + return; + } + + // Don't send a deny message if the player is holding an investigation tool + if (Sponge.getServer().getRunningTimeTicks() != lastInteractItemPrimaryTick || lastInteractItemCancelled != true) { + if (!PlayerUtil.getInstance().hasItemInOneHand(player, GriefDefenderPlugin.getInstance().investigationTool.getType())) { + this.sendInteractBlockDenyMessage(itemInHand, clickedBlock, claim, player, playerData, handType); + } + } + event.setCancelled(true); + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.stopTimingIfSync(); + return; + } + GDTimings.PLAYER_INTERACT_BLOCK_PRIMARY_EVENT.stopTimingIfSync(); + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onPlayerInteractBlockSecondary(InteractBlockEvent.Secondary event, @First Player player) { + // Run our item hook since Sponge no longer fires InteractItemEvent when targetting a non-air block + final HandType handType = event.getHandType(); + final ItemStack itemInHand = player.getItemInHand(handType).orElse(ItemStack.empty()); + if (handleItemInteract(event, player, player.getWorld(), itemInHand).isCancelled()) { + event.setCancelled(true); + return; + } + + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(player.getWorld().getUniqueId())) { + return; + } + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_BLOCK_SECONDARY.getName(), event.getTargetBlock().getState(), player.getWorld().getProperties())) { + return; + } + + GDTimings.PLAYER_INTERACT_BLOCK_SECONDARY_EVENT.startTimingIfSync(); + final BlockSnapshot clickedBlock = event.getTargetBlock(); + final Object source = player; + // Check if item is banned + final GDPlayerData playerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final Location<World> location = clickedBlock.getLocation().orElse(null); + + final GDClaim claim = this.dataStore.getClaimAt(location); + //GriefDefender.getPermissionManager().getFinalPermission(claim, Flags.ENTITY_SPAWN, source, target, user) + final TileEntity tileEntity = clickedBlock.getLocation().get().getTileEntity().orElse(null); + final TrustType trustType = (tileEntity != null && NMSUtil.getInstance().containsInventory(tileEntity)) ? TrustTypes.CONTAINER : TrustTypes.ACCESSOR; + if (GDFlags.INTERACT_BLOCK_SECONDARY && playerData != null) { + Tristate result = GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.INTERACT_BLOCK_SECONDARY, source, event.getTargetBlock(), player, trustType, true); + if (result == Tristate.FALSE) { + // if player is holding an item, check if it can be placed + if (GDFlags.BLOCK_PLACE && !itemInHand.isEmpty() && NMSUtil.getInstance().isItemBlock(itemInHand)) { + if (GriefDefenderPlugin.isTargetIdBlacklisted(Flags.BLOCK_PLACE.getName(), itemInHand, player.getWorld().getProperties())) { + GDTimings.PLAYER_INTERACT_BLOCK_SECONDARY_EVENT.stopTimingIfSync(); + return; + } + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, GDPermissions.BLOCK_PLACE, source, itemInHand, player, TrustTypes.BUILDER, true) == Tristate.TRUE) { + GDTimings.PLAYER_INTERACT_BLOCK_SECONDARY_EVENT.stopTimingIfSync(); + return; + } + } + // Don't send a deny message if the player is holding an investigation tool + if (Sponge.getServer().getRunningTimeTicks() != lastInteractItemSecondaryTick || lastInteractItemCancelled != true) { + if (!PlayerUtil.getInstance().hasItemInOneHand(player, GriefDefenderPlugin.getInstance().investigationTool.getType())) { + this.sendInteractBlockDenyMessage(itemInHand, clickedBlock, claim, player, playerData, handType); + } + } + if (handType == HandTypes.MAIN_HAND) { + NMSUtil.getInstance().closePlayerScreen(player); + } + + event.setCancelled(true); + GDTimings.PLAYER_INTERACT_BLOCK_SECONDARY_EVENT.stopTimingIfSync(); + return; + } + } + + GDTimings.PLAYER_INTERACT_BLOCK_SECONDARY_EVENT.stopTimingIfSync(); + } + + public InteractEvent handleItemInteract(InteractEvent event, Player player, World world, ItemStack itemInHand) { + final ItemType itemType = itemInHand.getType(); + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (!playerData.claimMode && (itemInHand.isEmpty() || NMSUtil.getInstance().isItemFood(itemType))) { + return event; + } + + final boolean primaryEvent = event instanceof InteractItemEvent.Primary || event instanceof InteractBlockEvent.Primary; + if (!GDFlags.INTERACT_ITEM_PRIMARY && primaryEvent || !GDFlags.INTERACT_ITEM_SECONDARY && !primaryEvent || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(world.getUniqueId())) { + return event; + } + + if (primaryEvent && GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_ITEM_PRIMARY.toString(), itemInHand.getType(), world.getProperties())) { + return event; + } + if (!primaryEvent && GriefDefenderPlugin.isTargetIdBlacklisted(Flags.INTERACT_ITEM_SECONDARY.toString(), itemInHand.getType(), world.getProperties())) { + return event; + } + + final Cause cause = event.getCause(); + final EventContext context = cause.getContext(); + final BlockSnapshot blockSnapshot = context.get(EventContextKeys.BLOCK_HIT).orElse(BlockSnapshot.NONE); + final Vector3d interactPoint = event.getInteractionPoint().orElse(null); + final Entity entity = context.get(EventContextKeys.ENTITY_HIT).orElse(null); + final Location<World> location = entity != null ? entity.getLocation() + : blockSnapshot != BlockSnapshot.NONE ? blockSnapshot.getLocation().get() + : interactPoint != null ? new Location<World>(world, interactPoint) + : player.getLocation(); + final GDClaim claim = this.dataStore.getClaimAt(location); + + final String ITEM_PERMISSION = primaryEvent ? GDPermissions.INTERACT_ITEM_PRIMARY : GDPermissions.INTERACT_ITEM_SECONDARY; + + if (playerData.claimMode || (!itemInHand.isEmpty() && (itemInHand.getType().equals(GriefDefenderPlugin.getInstance().modificationTool.getType()) || + itemInHand.getType().equals(GriefDefenderPlugin.getInstance().investigationTool.getType())))) { + GDPermissionManager.getInstance().addEventLogEntry(event, location, itemInHand, blockSnapshot == null ? entity : blockSnapshot, player, ITEM_PERMISSION, null, Tristate.TRUE); + event.setCancelled(true); + if (investigateClaim(event, player, blockSnapshot, itemInHand)) { + return event; + } + if (!primaryEvent) { + onPlayerHandleClaimCreateAction(event, blockSnapshot, player, itemInHand, playerData); + } + return event; + } + + if (GDPermissionManager.getInstance().getFinalPermission(event, location, claim, ITEM_PERMISSION, player, itemType, player, TrustTypes.ACCESSOR, true) == Tristate.FALSE) { + Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ITEM, + ImmutableMap.of( + "player", claim.getOwnerName(), + "item", itemInHand.getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + if (event instanceof InteractBlockEvent.Secondary) { + ((InteractBlockEvent.Secondary) event).setUseItemResult(SpongeUtil.getSpongeTristate(Tristate.FALSE)); + } else { + event.setCancelled(true); + } + lastInteractItemCancelled = true; + } + return event; + } + + private void onPlayerHandleClaimCreateAction(InteractEvent event, BlockSnapshot targetBlock, Player player, ItemStack itemInHand, GDPlayerData playerData) { + if (player.get(Keys.IS_SNEAKING).get() && (event instanceof InteractBlockEvent.Secondary || event instanceof InteractItemEvent.Secondary)) { + playerData.revertActiveVisual(player); + // check for any active WECUI visuals + if (this.worldEditProvider != null) { + this.worldEditProvider.revertVisuals(player, playerData, null); + } + playerData.lastShovelLocation = null; + playerData.endShovelLocation = null; + playerData.claimResizing = null; + playerData.shovelMode = ShovelTypes.BASIC; + return; + } + + /*if (!playerData.claimMode) { + GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()); + ItemType materialInHand = player.getItemInHand(handType).get().getType(); + if (!materialInHand.getId().equals(activeConfig.getConfig().claim.modificationTool)) { + return; + } + }*/ + + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.startTimingIfSync(); + BlockSnapshot clickedBlock = targetBlock; + Location<World> location = clickedBlock.getLocation().orElse(null); + + if (clickedBlock.getState().getType() == BlockTypes.AIR) { + boolean ignoreAir = false; + if (this.worldEditProvider != null) { + // Ignore air so players can use client-side WECUI block target which uses max reach distance + if (this.worldEditProvider.hasCUISupport(player) && playerData.getClaimCreateMode() == CreateModeTypes.VOLUME && playerData.lastShovelLocation != null) { + ignoreAir = true; + } + } + final int distance = !ignoreAir ? 100 : NMSUtil.getInstance().getPlayerBlockReachDistance(player); + location = BlockUtil.getInstance().getTargetBlock(player, playerData, distance, ignoreAir).orElse(null); + if (location == null) { + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + } + + if (location == null) { + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTimingIfSync(); + return; + } + + event.setCancelled(true); + if (playerData.shovelMode == ShovelTypes.RESTORE) { + if (true) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().FEATURE_NOT_AVAILABLE); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + final GDClaim claim = this.dataStore.getClaimAtPlayer(location, playerData, true); + if (!claim.isUserTrusted(player, TrustTypes.MANAGER)) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BLOCK_CLAIMED, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + ClaimVisual claimVisual = new ClaimVisual(claim, ClaimVisual.ERROR); + claimVisual.createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + claimVisual.apply(player); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + Chunk chunk = player.getWorld().getChunk(location.getBlockX() >> 4, 0, location.getBlockZ() >> 4).get(); + int miny = location.getBlockY(); + World world = chunk.getWorld(); + final Chunk newChunk = world.regenerateChunk(chunk.getPosition().getX(), 0, chunk.getPosition().getZ()).orElse(null); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + if (!playerData.canCreateClaim(player, true)) { + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + if (playerData.claimResizing != null) { + handleResizeFinish(event, player, location, playerData); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + GDClaim claim = this.dataStore.getClaimAtPlayer(location, playerData, true); + if (!claim.isWilderness()) { + Component noEditReason = claim.allowEdit(player); + if (noEditReason != null) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_OVERLAP_PLAYER, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + ClaimVisual visualization = new ClaimVisual(claim, ClaimVisual.ERROR); + visualization.createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + visualization.apply(player); + Set<Claim> claims = new HashSet<>(); + claims.add(claim); + CommandHelper.showClaims(player, claims, location.getBlockY(), true); + } else if (playerData.lastShovelLocation == null && BlockUtil.getInstance().clickedClaimCorner(claim, location.getBlockPosition())) { + handleResizeStart(event, player, location, playerData, claim); + } else if ((playerData.shovelMode == ShovelTypes.SUBDIVISION + || ((claim.isTown() || claim.isAdminClaim()) && (playerData.lastShovelLocation == null || playerData.claimSubdividing != null)) && playerData.shovelMode != ShovelTypes.TOWN)) { + if (claim.getTownClaim() != null && playerData.shovelMode == ShovelTypes.TOWN) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(claim); + CommandHelper.showClaims(player, claims, location.getBlockY(), true); + } else if (playerData.lastShovelLocation == null) { + createSubdivisionStart(event, player, itemInHand, location, playerData, claim); + } else if (playerData.claimSubdividing != null) { + createSubdivisionFinish(event, player, location, playerData, claim); + } + } else { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP); + Set<Claim> claims = new HashSet<>(); + claims.add(claim); + CommandHelper.showClaims(player, claims, location.getBlockY(), true); + } + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } else if (playerData.shovelMode == ShovelTypes.SUBDIVISION && playerData.lastShovelLocation != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_SUBDIVISION_FAIL); + playerData.lastShovelLocation = null; + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + Location<World> lastShovelLocation = playerData.lastShovelLocation; + if (lastShovelLocation == null) { + createClaimStart(event, player, itemInHand, location, playerData, claim); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + return; + } + + createClaimFinish(event, player, location, playerData, claim); + GDTimings.PLAYER_HANDLE_SHOVEL_ACTION.stopTiming(); + } + + private void createClaimStart(InteractEvent event, Player player, ItemStack itemInHand, Location<World> location, GDPlayerData playerData, GDClaim claim) { + if (!player.hasPermission(GDPermissions.BYPASS_CLAIM_LIMIT)) { + int createClaimLimit = -1; + if (playerData.shovelMode == ShovelTypes.BASIC && (claim.isAdminClaim() || claim.isTown() || claim.isWilderness())) { + createClaimLimit = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.CREATE_LIMIT, claim).intValue(); + } else if (playerData.shovelMode == ShovelTypes.TOWN && (claim.isAdminClaim() || claim.isWilderness())) { + createClaimLimit = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.CREATE_LIMIT, claim).intValue(); + } else if (playerData.shovelMode == ShovelTypes.SUBDIVISION && !claim.isWilderness()) { + createClaimLimit = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.CREATE_LIMIT, claim).intValue(); + } + + if (createClaimLimit > 0 && createClaimLimit < (playerData.getInternalClaims().size() + 1)) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_FAILED_CLAIM_LIMIT)); + return; + } + } + + final int minClaimLevel = playerData.getMinClaimLevel(); + if (playerData.shovelMode != ShovelTypes.ADMIN && location.getBlockY() < minClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_BELOW_LEVEL, + ImmutableMap.of( + "limit", minClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + final int maxClaimLevel = playerData.getMaxClaimLevel(); + if (playerData.shovelMode != ShovelTypes.ADMIN && location.getBlockY() > maxClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_ABOVE_LEVEL, + ImmutableMap.of( + "limit", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + if (playerData.shovelMode == ShovelTypes.SUBDIVISION && claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_SUBDIVISION_FAIL); + return; + } + + final ClaimType type = PlayerUtil.getInstance().getClaimTypeFromShovel(playerData.shovelMode); + if ((type == ClaimTypes.BASIC || type == ClaimTypes.TOWN) && GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + // Check current economy mode cost + final Double economyBlockCost = playerData.getInternalEconomyBlockCost(); + if (economyBlockCost == null || economyBlockCost <= 0) { + GriefDefenderPlugin.sendMessage(player, TextComponent.builder().color(TextColor.RED) + .append("Economy mode is enabled but the current cost for blocks is ") + .append("0", TextColor.GOLD) + .append("\nRaise the value for option 'economy-block-cost' in config or via '") + .append("/gd option claim", TextColor.WHITE) + .append("' command.", TextColor.RED) + .build()); + return; + } + } + + playerData.lastShovelLocation = location; + Component message = null; + if (playerData.claimMode) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_MODE_START, + ImmutableMap.of( + "type", PlayerUtil.getInstance().getClaimTypeComponentFromShovel(playerData.shovelMode))); + } else { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_START, + ImmutableMap.of( + "type", PlayerUtil.getInstance().getClaimTypeComponentFromShovel(playerData.shovelMode), + "item", itemInHand.getType().getId())); + } + GriefDefenderPlugin.sendMessage(player, message); + ClaimVisual visual = ClaimVisual.fromClick(location, location.getBlockY(), PlayerUtil.getInstance().getVisualTypeFromShovel(playerData.shovelMode), player, playerData); + visual.apply(player, false); + } + + private void createClaimFinish(InteractEvent event, Player player, Location<World> location, GDPlayerData playerData, GDClaim claim) { + Location<World> lastShovelLocation = playerData.lastShovelLocation; + final GDClaim firstClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData.lastShovelLocation, playerData, true); + final GDClaim clickedClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(location, playerData, true); + if (!firstClaim.equals(clickedClaim)) { + final GDClaim overlapClaim = firstClaim.isWilderness() ? clickedClaim : firstClaim; + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showClaims(player, claims, location.getBlockY(), true); + return; + } + + final boolean cuboid = playerData.getClaimCreateMode() == CreateModeTypes.VOLUME; + Vector3i lesserBoundaryCorner = new Vector3i( + lastShovelLocation.getBlockX(), + cuboid ? lastShovelLocation.getBlockY() : playerData.getMinClaimLevel(), + lastShovelLocation.getBlockZ()); + Vector3i greaterBoundaryCorner = new Vector3i( + location.getBlockX(), + cuboid ? location.getBlockY() : playerData.getMaxClaimLevel(), + location.getBlockZ()); + + final ClaimType type = PlayerUtil.getInstance().getClaimTypeFromShovel(playerData.shovelMode); + if ((type == ClaimTypes.BASIC || type == ClaimTypes.TOWN) && GriefDefenderPlugin.getGlobalConfig().getConfig().economy.economyMode) { + EconomyUtil.getInstance().economyCreateClaimConfirmation(player, playerData, location.getBlockY(), lesserBoundaryCorner, greaterBoundaryCorner, PlayerUtil.getInstance().getClaimTypeFromShovel(playerData.shovelMode), + cuboid, playerData.claimSubdividing); + return; + } + + GDCauseStackManager.getInstance().pushCause(player); + ClaimResult result = this.dataStore.createClaim( + player.getWorld(), + lesserBoundaryCorner, + greaterBoundaryCorner, + type, player.getUniqueId(), cuboid); + GDCauseStackManager.getInstance().popCause(); + GDClaim gdClaim = (GDClaim) result.getClaim().orElse(null); + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) result.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showOverlapClaims(player, claims, location.getBlockY()); + } else if (result.getResultType() == ClaimResultType.CLAIM_EVENT_CANCELLED) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_CANCEL); + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_FAILED_RESULT, + ImmutableMap.of("reason", result.getResultType()))); + } + return; + } else { + playerData.lastShovelLocation = null; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_SUCCESS, + ImmutableMap.of( + "type", gdClaim.getFriendlyNameType(true))); + GriefDefenderPlugin.sendMessage(player, message); + final WorldEditProvider worldEditProvider = GriefDefenderPlugin.getInstance().worldEditProvider; + if (worldEditProvider != null) { + worldEditProvider.stopVisualDrag(player); + worldEditProvider.visualizeClaim(gdClaim, player, playerData, false); + } + gdClaim.getVisualizer().createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + gdClaim.getVisualizer().apply(player, false); + } + } + + private void createSubdivisionStart(InteractEvent event, Player player, ItemStack itemInHand, Location<World> location, GDPlayerData playerData, GDClaim claim) { + final int minClaimLevel = playerData.getMinClaimLevel(); + if (playerData.shovelMode != ShovelTypes.ADMIN && location.getBlockY() < minClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_BELOW_LEVEL, + ImmutableMap.of( + "limit", minClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + final int maxClaimLevel = playerData.getMaxClaimLevel(); + if (playerData.shovelMode != ShovelTypes.ADMIN && location.getBlockY() > maxClaimLevel) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_ABOVE_LEVEL, + ImmutableMap.of( + "limit", maxClaimLevel)); + GriefDefenderPlugin.sendMessage(player, message); + return; + } + + if (claim.isSubdivision()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().RESIZE_OVERLAP_SUBDIVISION); + } else { + Component message = null; + if (playerData.claimMode) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_START, + ImmutableMap.of( + "type", playerData.shovelMode.getName())); + } else { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_START, + ImmutableMap.of( + "type", playerData.shovelMode.getName(), + "item", itemInHand.getType().getId())); + } + GriefDefenderPlugin.sendMessage(player, message); + playerData.lastShovelLocation = location; + playerData.claimSubdividing = claim; + ClaimVisual visualization = ClaimVisual.fromClick(location, location.getBlockY(), PlayerUtil.getInstance().getVisualTypeFromShovel(playerData.shovelMode), player, playerData); + visualization.apply(player, false); + } + } + + private void createSubdivisionFinish(InteractEvent event, Player player, Location<World> location, GDPlayerData playerData, GDClaim claim) { + final GDClaim clickedClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimAt(location); + if (clickedClaim == null || !playerData.claimSubdividing.getUniqueId().equals(clickedClaim.getUniqueId())) { + if (clickedClaim != null) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + final GDClaim overlapClaim = playerData.claimSubdividing; + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showClaims(player, claims, location.getBlockY(), true); + } + + return; + } + + Vector3i lesserBoundaryCorner = new Vector3i(playerData.lastShovelLocation.getBlockX(), + playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? playerData.lastShovelLocation.getBlockY() : playerData.getMinClaimLevel(), + playerData.lastShovelLocation.getBlockZ()); + Vector3i greaterBoundaryCorner = new Vector3i(location.getBlockX(), + playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? location.getBlockY() : playerData.getMaxClaimLevel(), + location.getBlockZ()); + + GDCauseStackManager.getInstance().pushCause(player); + ClaimResult result = this.dataStore.createClaim(player.getWorld(), + lesserBoundaryCorner, greaterBoundaryCorner, PlayerUtil.getInstance().getClaimTypeFromShovel(playerData.shovelMode), + player.getUniqueId(), playerData.getClaimCreateMode() == CreateModeTypes.VOLUME, playerData.claimSubdividing); + GDCauseStackManager.getInstance().popCause(); + GDClaim gdClaim = (GDClaim) result.getClaim().orElse(null); + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(gdClaim); + CommandHelper.showOverlapClaims(player, claims, location.getBlockY()); + } + event.setCancelled(true); + return; + } else { + playerData.lastShovelLocation = null; + playerData.claimSubdividing = null; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_SUCCESS, ImmutableMap.of( + "type", gdClaim.getFriendlyNameType(true))); + GriefDefenderPlugin.sendMessage(player, message); + gdClaim.getVisualizer().createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + gdClaim.getVisualizer().apply(player, false); + final WorldEditProvider worldEditProvider = GriefDefenderPlugin.getInstance().worldEditProvider; + if (worldEditProvider != null) { + worldEditProvider.stopVisualDrag(player); + worldEditProvider.visualizeClaim(gdClaim, player, playerData, false); + } + } + } + + private void handleResizeStart(InteractEvent event, Player player, Location<World> location, GDPlayerData playerData, GDClaim claim) { + boolean playerCanResize = true; + if (!player.hasPermission(GDPermissions.CLAIM_RESIZE_ALL) + && !playerData.canIgnoreClaim(claim) + && !claim.isUserTrusted(player.getUniqueId(), TrustTypes.MANAGER)) { + + if (claim.isAdminClaim()) { + if (!playerData.canManageAdminClaims) { + playerCanResize = false; + } + } else if (!player.getUniqueId().equals(claim.getOwnerUniqueId())) { + playerCanResize = false; + } + if (!playerCanResize) { + if (claim.parent != null) { + if (claim.parent.isAdminClaim() && claim.isSubdivision()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_ADMIN_SUBDIVISION); + } else if (claim.parent.isBasicClaim() && claim.isSubdivision()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_BASIC_SUBDIVISION); + } else if (claim.isTown()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_TOWN); + } else if (claim.isAdminClaim()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_ADMIN); + } else { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_BASIC); + } + } else if (claim.isTown()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_TOWN); + } else if (claim.isAdminClaim()) { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_ADMIN); + } else { + playerCanResize = player.hasPermission(GDPermissions.CLAIM_RESIZE_BASIC); + } + } + } + + if (!claim.getInternalClaimData().isResizable() || !playerCanResize) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_CLAIM_RESIZE); + return; + } + + playerData.claimResizing = claim; + playerData.lastShovelLocation = location; + if (GriefDefenderPlugin.getInstance().worldEditProvider != null) { + // get opposite corner + final int x = playerData.lastShovelLocation.getBlockX() == claim.lesserBoundaryCorner.getX() ? claim.greaterBoundaryCorner.getX() : claim.lesserBoundaryCorner.getX(); + final int y = playerData.lastShovelLocation.getBlockY() == claim.lesserBoundaryCorner.getY() ? claim.greaterBoundaryCorner.getY() : claim.lesserBoundaryCorner.getY(); + final int z = playerData.lastShovelLocation.getBlockZ() == claim.lesserBoundaryCorner.getZ() ? claim.greaterBoundaryCorner.getZ() : claim.lesserBoundaryCorner.getZ(); + GriefDefenderPlugin.getInstance().worldEditProvider.visualizeClaim(claim, new Vector3i(x, y, z), playerData.lastShovelLocation.getBlockPosition(), player, playerData, false); + } + // Show visual block for resize corner click + ClaimVisual visual = ClaimVisual.fromClick(location, location.getBlockY(), PlayerUtil.getInstance().getVisualTypeFromShovel(playerData.shovelMode), player, playerData); + visual.apply(player, false); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().RESIZE_START); + } + + private void handleResizeFinish(InteractEvent event, Player player, Location<World> location, GDPlayerData playerData) { + if (location.equals(playerData.lastShovelLocation)) { + return; + } + + playerData.endShovelLocation = location; + int newx1, newx2, newz1, newz2, newy1, newy2; + int smallX = 0, smallY = 0, smallZ = 0, bigX = 0, bigY = 0, bigZ = 0; + + newx1 = playerData.lastShovelLocation.getBlockX(); + newx2 = location.getBlockX(); + newy1 = playerData.lastShovelLocation.getBlockY(); + newy2 = location.getBlockY(); + newz1 = playerData.lastShovelLocation.getBlockZ(); + newz2 = location.getBlockZ(); + Vector3i lesserBoundaryCorner = playerData.claimResizing.getLesserBoundaryCorner(); + Vector3i greaterBoundaryCorner = playerData.claimResizing.getGreaterBoundaryCorner(); + smallX = lesserBoundaryCorner.getX(); + smallY = lesserBoundaryCorner.getY(); + smallZ = lesserBoundaryCorner.getZ(); + bigX = greaterBoundaryCorner.getX(); + bigY = greaterBoundaryCorner.getY(); + bigZ = greaterBoundaryCorner.getZ(); + + if (newx1 == smallX) { + smallX = newx2; + } else { + bigX = newx2; + } + + if (newy1 == smallY) { + smallY = newy2; + } else { + bigY = newy2; + } + + if (newz1 == smallZ) { + smallZ = newz2; + } else { + bigZ = newz2; + } + + ClaimResult claimResult = null; + GDCauseStackManager.getInstance().pushCause(player); + claimResult = playerData.claimResizing.resize(smallX, bigX, smallY, bigY, smallZ, bigZ); + GDCauseStackManager.getInstance().popCause(); + if (claimResult.successful()) { + Claim claim = (GDClaim) claimResult.getClaim().get(); + int claimBlocksRemaining = playerData.getRemainingClaimBlocks();; + if (!playerData.claimResizing.isAdminClaim()) { + UUID ownerID = playerData.claimResizing.getOwnerUniqueId(); + if (playerData.claimResizing.parent != null) { + ownerID = playerData.claimResizing.parent.getOwnerUniqueId(); + } + + if (ownerID.equals(player.getUniqueId())) { + claimBlocksRemaining = playerData.getRemainingClaimBlocks(); + } else { + GDPlayerData ownerData = this.dataStore.getOrCreatePlayerData(player.getWorld(), ownerID); + claimBlocksRemaining = ownerData.getRemainingClaimBlocks(); + Optional<User> owner = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(ownerID); + if (owner.isPresent() && !owner.get().isOnline()) { + this.dataStore.clearCachedPlayerData(player.getWorld().getUniqueId(), ownerID); + } + } + } + + playerData.claimResizing = null; + playerData.lastShovelLocation = null; + playerData.endShovelLocation = null; + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount != null) { + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.intValue()), + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_MODE_RESIZE_SUCCESS_2D, params)); + } + } + } else { + if (GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME) { + final double claimableChunks = claimBlocksRemaining / 65536.0; + final Map<String, Object> params = ImmutableMap.of( + "chunk-amount", Math.round(claimableChunks * 100.0)/100.0, + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_3D, params)); + } else { + final Map<String, Object> params = ImmutableMap.of( + "block-amount", claimBlocksRemaining); + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.RESIZE_SUCCESS_2D, params)); + } + } + playerData.revertActiveVisual(player); + ((GDClaim) claim).getVisualizer().resetVisuals(); + ((GDClaim) claim).getVisualizer().createClaimBlockVisuals(location.getBlockY(), player.getLocation(), playerData); + ((GDClaim) claim).getVisualizer().apply(player); + if (this.worldEditProvider != null) { + this.worldEditProvider.visualizeClaim(claim, player, playerData, false); + } + } else { + if (claimResult.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) claimResult.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().RESIZE_OVERLAP); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showOverlapClaims(player, claims, location.getBlockY()); + } else { + if (!claimResult.getMessage().isPresent()) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_NOT_YOURS); + } + } + + playerData.claimSubdividing = null; + event.setCancelled(true); + } + } + + private boolean investigateClaim(InteractEvent event, Player player, BlockSnapshot clickedBlock, ItemStack itemInHand) { + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + if (playerData.claimMode && (event instanceof InteractItemEvent.Secondary || event instanceof InteractBlockEvent.Secondary)) { + if (player.get(Keys.IS_SNEAKING).get()) { + return true; + } + // claim mode inspects with left-click + return false; + } + if (!playerData.claimMode && (itemInHand.isEmpty() || itemInHand.getType() != GriefDefenderPlugin.getInstance().investigationTool.getType())) { + return false; + } + + if (event instanceof InteractItemEvent.Primary || event instanceof InteractBlockEvent.Primary) { + if (!playerData.claimMode || !playerData.visualBlocks.isEmpty()) { + playerData.revertActiveVisual(player); + if (this.worldEditProvider != null) { + this.worldEditProvider.revertVisuals(player, playerData, null); + } + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTiming(); + return false; + } + } + + GDTimings.PLAYER_INVESTIGATE_CLAIM.startTimingIfSync(); + // if holding shift (sneaking), show all claims in area + GDClaim claim = null; + if (clickedBlock.getState().getType().equals(BlockTypes.AIR)) { + claim = this.findNearbyClaim(player); + // if holding shift (sneaking), show all claims in area + if (player.get(Keys.IS_SNEAKING).get()) { + if (!playerData.canIgnoreClaim(claim) && !player.hasPermission(GDPermissions.VISUALIZE_CLAIMS_NEARBY)) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().PERMISSION_VISUAL_CLAIMS_NEARBY); + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTimingIfSync(); + return false; + } + + Location<World> nearbyLocation = playerData.lastValidInspectLocation != null ? playerData.lastValidInspectLocation : player.getLocation(); + Set<Claim> claims = BlockUtil.getInstance().getNearbyClaims(nearbyLocation); + int height = (int) (playerData.lastValidInspectLocation != null ? playerData.lastValidInspectLocation.getBlockY() : PlayerUtil.getInstance().getEyeHeight(player)); + boolean hideBorders = this.worldEditProvider != null && + this.worldEditProvider.hasCUISupport(player) && + GriefDefenderPlugin.getActiveConfig(player.getWorld().getUniqueId()).getConfig().visual.hideBorders; + if (!hideBorders) { + ClaimVisual visualization = ClaimVisual.fromClaims(claims, PlayerUtil.getInstance().getVisualClaimHeight(playerData, height), player.getLocation(), playerData, null); + visualization.apply(player); + } + + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_SHOW_NEARBY, + ImmutableMap.of( + "amount", claims.size())); + GriefDefenderPlugin.sendMessage(player, message); + if (!claims.isEmpty()) { + + if (this.worldEditProvider != null) { + worldEditProvider.revertVisuals(player, playerData, null); + worldEditProvider.visualizeClaims(claims, player, playerData, true); + } + CommandHelper.showClaims(player, claims); + } + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTimingIfSync(); + return true; + } + if (claim != null && claim.isWilderness()) { + playerData.lastValidInspectLocation = null; + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTimingIfSync(); + return false; + } + } else { + claim = this.dataStore.getClaimAt(clickedBlock.getLocation().get()); + if (claim.isWilderness()) { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BLOCK_NOT_CLAIMED)); + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTimingIfSync(); + return false; + } + } + + // visualize boundary + if (claim.getUniqueId() != playerData.visualClaimId) { + int height = playerData.lastValidInspectLocation != null ? playerData.lastValidInspectLocation.getBlockY() : clickedBlock.getLocation().get().getBlockY(); + claim.getVisualizer().createClaimBlockVisuals(playerData.getClaimCreateMode() == CreateModeTypes.VOLUME ? height : PlayerUtil.getInstance().getEyeHeight(player), player.getLocation(), playerData); + claim.getVisualizer().apply(player); + if (this.worldEditProvider != null) { + worldEditProvider.visualizeClaim(claim, player, playerData, true); + } + Set<Claim> claims = new HashSet<>(); + claims.add(claim); + CommandHelper.showClaims(player, claims); + } + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BLOCK_CLAIMED, + ImmutableMap.of( + "player", claim.getOwnerName())); + GriefDefenderPlugin.sendMessage(player, message); + + GDTimings.PLAYER_INVESTIGATE_CLAIM.stopTimingIfSync(); + return true; + } + + private GDClaim findNearbyClaim(Player player) { + int maxDistance = GriefDefenderPlugin.getInstance().maxInspectionDistance; + BlockRay<World> blockRay = BlockRay.from(player).distanceLimit(maxDistance).build(); + GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + GDClaim claim = null; + int count = 0; + while (blockRay.hasNext()) { + BlockRayHit<World> blockRayHit = blockRay.next(); + Location<World> location = blockRayHit.getLocation(); + claim = this.dataStore.getClaimAt(location); + if (claim != null && !claim.isWilderness() && (playerData.visualBlocks == null || (claim.getUniqueId() != playerData.visualClaimId))) { + playerData.lastValidInspectLocation = location; + return claim; + } + + BlockType blockType = location.getBlockType(); + if (blockType != BlockTypes.AIR && blockType != BlockTypes.TALLGRASS) { + break; + } + count++; + } + + if (count == maxDistance) { + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CLAIM_TOO_FAR); + } else if (claim != null && claim.isWilderness()){ + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.BLOCK_NOT_CLAIMED)); + } + + return claim; + } + + private void sendInteractEntityDenyMessage(ItemStack playerItem, Entity entity, GDClaim claim, Player player, HandType handType) { + if (entity instanceof Player || (claim.getData() != null && !claim.getData().allowDenyMessages())) { + return; + } + + final String entityId = entity.getType() != null ? entity.getType().getId() : NMSUtil.getInstance().getEntityName(entity); + if (playerItem == null || playerItem == ItemTypes.NONE || playerItem.isEmpty()) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ENTITY, ImmutableMap.of( + "player", claim.getOwnerName(), + "entity", entityId)); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ITEM_ENTITY, ImmutableMap.of( + "item", playerItem.getType().getId(), + "entity", entityId)); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } + } + + private void sendInteractBlockDenyMessage(ItemStack playerItem, BlockSnapshot blockSnapshot, GDClaim claim, Player player, GDPlayerData playerData, HandType handType) { + if (claim.getData() != null && !claim.getData().allowDenyMessages()) { + return; + } + + if (playerData != null && claim.getData() != null && claim.getData().isExpired() /*&& GriefDefenderPlugin.getActiveConfig(player.getWorld().getProperties()).getConfig().claim.bankTaxSystem*/) { + playerData.sendTaxExpireMessage(player, claim); + } else if (playerItem == null || playerItem == ItemTypes.NONE || playerItem.isEmpty()) { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_BLOCK, + ImmutableMap.of( + "player", claim.getOwnerName(), + "block", blockSnapshot.getState().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } else { + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.PERMISSION_INTERACT_ITEM_BLOCK, + ImmutableMap.of( + "item", playerItem.getType().getId(), + "block", blockSnapshot.getState().getType().getId())); + GriefDefenderPlugin.sendClaimDenyMessage(claim, player, message); + } + if (handType == HandTypes.MAIN_HAND) { + NMSUtil.getInstance().closePlayerScreen(player); + } + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/listener/WorldEventHandler.java b/sponge/src/main/java/com/griefdefender/listener/WorldEventHandler.java new file mode 100644 index 0000000..af93661 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/listener/WorldEventHandler.java @@ -0,0 +1,97 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.listener; + +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDTimings; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.task.TaxApplyTask; +import com.griefdefender.util.TaskUtil; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.world.LoadWorldEvent; +import org.spongepowered.api.event.world.SaveWorldEvent; +import org.spongepowered.api.event.world.UnloadWorldEvent; +import org.spongepowered.api.event.world.chunk.LoadChunkEvent; +import org.spongepowered.api.event.world.chunk.UnloadChunkEvent; +import org.spongepowered.common.SpongeImpl; + +import java.util.concurrent.TimeUnit; + +public class WorldEventHandler { + + @Listener(order = Order.EARLY, beforeModifications = true) + public void onWorldLoad(LoadWorldEvent event) { + if (!SpongeImpl.getServer().isServerRunning() || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetWorld().getUniqueId())) { + return; + } + + GDTimings.WORLD_LOAD_EVENT.startTimingIfSync(); + GriefDefenderPlugin.getInstance().dataStore.registerWorld(event.getTargetWorld()); + GriefDefenderPlugin.getInstance().dataStore.loadWorldData(event.getTargetWorld()); + + NMSUtil.getInstance().addEntityRemovalListener(event.getTargetWorld()); + GDTimings.WORLD_LOAD_EVENT.stopTimingIfSync(); + if (!GriefDefenderPlugin.getActiveConfig(event.getTargetWorld().getProperties()).getConfig().claim.bankTaxSystem) { + return; + } + if (GriefDefenderPlugin.getInstance().economyService.isPresent()) { + // run tax task + TaxApplyTask taxTask = new TaxApplyTask(event.getTargetWorld().getProperties()); + int taxHour = GriefDefenderPlugin.getActiveConfig(event.getTargetWorld().getProperties()).getConfig().claim.taxApplyHour; + long delay = TaskUtil.computeDelay(taxHour, 0, 0); + Sponge.getScheduler().createTaskBuilder().delay(delay, TimeUnit.SECONDS).interval(1, TimeUnit.DAYS).execute(taxTask).submit(GDBootstrap.getInstance()); + } + } + + @Listener(order = Order.FIRST, beforeModifications = true) + public void onWorldUnload(UnloadWorldEvent event) { + if (!SpongeImpl.getServer().isServerRunning() || !GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetWorld().getUniqueId())) { + return; + } + + GriefDefenderPlugin.getInstance().dataStore.removeClaimWorldManager(event.getTargetWorld().getProperties()); + } + + @Listener + public void onWorldSave(SaveWorldEvent.Post event) { + if (!GriefDefenderPlugin.getInstance().claimsEnabledForWorld(event.getTargetWorld().getUniqueId())) { + return; + } + + GDTimings.WORLD_SAVE_EVENT.startTimingIfSync(); + GDClaimManager claimWorldManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(event.getTargetWorld().getUniqueId()); + if (claimWorldManager == null) { + GDTimings.WORLD_SAVE_EVENT.stopTimingIfSync(); + return; + } + + claimWorldManager.save(); + GDTimings.WORLD_SAVE_EVENT.stopTimingIfSync(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/migrator/GPBukkitMigrator.java b/sponge/src/main/java/com/griefdefender/migrator/GPBukkitMigrator.java new file mode 100644 index 0000000..a51e4d8 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/migrator/GPBukkitMigrator.java @@ -0,0 +1,713 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.migrator; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.configuration.ClaimDataConfig; +import com.griefdefender.configuration.ClaimStorageData; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.PermissionUtil; + +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.spongepowered.api.world.World; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.ArrayList; +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.concurrent.ConcurrentHashMap; + +public class GPBukkitMigrator { + + private static final File gpBukkitPlayerDataMigrated = BaseStorage.globalPlayerDataPath.resolve("_bukkitMigrated").toFile(); + private static final Path gpFlags = Paths.get("plugins", "GPFlags", "flags.yml"); + private static final Map<Integer, UUID> idToUUID = new ConcurrentHashMap<>(); + private static final Map<UUID, ClaimStorageData> claimStorageMap = new ConcurrentHashMap<>(); + private static final GDPermissionHolder DEFAULT_HOLDER = GriefDefenderPlugin.DEFAULT_HOLDER; + private static int count; + + private static final String FLAG_CHANGE_BIOME = "changebiome"; + private static final String FLAG_COMMAND_BLACKLIST = "commandblacklist"; + private static final String FLAG_COMMAND_WHITELIST = "commandwhitelist"; + private static final String FLAG_ENTER_COMMAND = "entercommand"; + private static final String FLAG_ENTER_COMMAND_MEMBERS = "entercommandmembers"; + private static final String FLAG_ENTER_COMMAND_OWNER = "entercommandowner"; + private static final String FLAG_ENTER_MESSAGE = "entermessage"; + private static final String FLAG_ENTER_PLAYER_COMMAND = "enterplayercommand"; + private static final String FLAG_EXIT_COMMAND = "exitcommand"; + private static final String FLAG_EXIT_COMMAND_MEMBERS = "exitcommandmembers"; + private static final String FLAG_EXIT_COMMAND_OWNER = "exitcommandowner"; + private static final String FLAG_EXIT_MESSAGE = "exitmessage"; + private static final String FLAG_EXIT_PLAYER_COMMAND = "exitplayercommand"; + private static final String FLAG_HEALTH_REGEN = "healthregen"; + private static final String FLAG_INFINITE_ARROWS = "infinitearrows"; + private static final String FLAG_KEEP_INVENTORY = "keepinventory"; + private static final String FLAG_KEEP_LEVEL = "keeplevel"; + private static final String FLAG_NETHER_PORTAL_CONSOLE_COMMAND = "netherportalconsolecommand"; + private static final String FLAG_NETHER_PORTAL_PLAYER_COMMAND = "netherportalplayercommand"; + private static final String FLAG_NO_CHORUS_FRUIT = "nochorusfruit"; + private static final String FLAG_NO_COMBAT_LOOT = "nocombatloot"; + private static final String FLAG_NO_ENDER_PEARL = "noenderpearl"; + private static final String FLAG_NO_ENTER = "noenter"; + private static final String FLAG_NO_ENTER_PLAYER = "noenterplayer"; + private static final String FLAG_NO_EXPIRATION = "noexpiration"; + private static final String FLAG_NO_EXPLOSION_DAMAGE = "noexplosiondamage"; + private static final String FLAG_NO_FALL_DAMAGE = "nofalldamage"; + private static final String FLAG_NO_FIRE_DAMAGE = "nofiredamage"; + private static final String FLAG_NO_FIRE_SPREAD = "nofirespread"; + private static final String FLAG_NO_FLIGHT = "noflight"; + private static final String FLAG_NO_FLUID_FLOW = "nofluidflow"; + private static final String FLAG_NO_GROWTH = "nogrowth"; + private static final String FLAG_NO_HUNGER = "nohunger"; + private static final String FLAG_NO_ICE_FORM = "noiceform"; + private static final String FLAG_NO_ITEM_DAMAGE = "noitemdamage"; + private static final String FLAG_NO_ITEM_DROP = "noitemdrop"; + private static final String FLAG_NO_ITEM_PICKUP = "noitempickup"; + private static final String FLAG_NO_LEAF_DECAY = "noleafdecay"; + private static final String FLAG_NO_LOOT_PROTECTION = "nolootprotection"; + private static final String FLAG_NO_MOB_DAMAGE = "nomobdamage"; + private static final String FLAG_NO_MOB_SPAWNS = "nomobspawns"; + private static final String FLAG_NO_MOB_SPAWNS_TYPE = "nomobspawnstype"; + private static final String FLAG_NO_OPEN_DOORS = "noopendoors"; + private static final String FLAG_NO_PET_DAMAGE = "nopetdamage"; + private static final String FLAG_NO_PLAYER_DAMAGE = "noplayerdamage"; + private static final String FLAG_NO_SNOW_FORM = "nosnowform"; + private static final String FLAG_NO_VEHICLE = "novehicle"; + private static final String FLAG_NO_VINE_GROWTH = "novinegrowth"; + private static final String FLAG_NO_WEATHER_CHANGE = "noweatherchange"; + private static final String FLAG_OWNER_FLY = "ownerfly"; + private static final String FLAG_OWNER_MEMBER_FLY = "ownermemberfly"; + private static final String FLAG_PLAYER_GAMEMODE = "playergamemode"; + private static final String FLAG_PLAYER_TIME = "playertime"; + private static final String FLAG_PLAYER_WEATHER = "playerweather"; + private static final String FLAG_RESPAWN_LOCATION = "respawnlocation"; + private static final String FLAG_SPLEEF_ARENA = "spleefarena"; + private static final String FLAG_TRAPPED_DESTINATION = "trappeddestination"; + + private static final Context SOURCE_PLAYER = new Context(ContextKeys.SOURCE, "player"); + private static final Context TARGET_ICE_FORM = new Context(ContextKeys.TARGET, "ice"); + private static final Context TARGET_PLAYER = new Context(ContextKeys.TARGET, "player"); + private static final Context TARGET_SNOW_LAYER = new Context("target", "snow_layer"); + + public static void migrate(World world, Path gpClassicDataPath) throws FileNotFoundException, ClassNotFoundException { + count = 0; + GriefDefenderPlugin.getInstance().getLogger().info("Starting GriefPrevention data migration for world " + world.getName() + "..."); + // Migrate playerdata first + migratePlayerData(world); + File[] files = gpClassicDataPath.toFile().listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + try { + int claimId = Integer.parseInt(file.getName().replaceFirst("[.][^.]+$", "")); + idToUUID.put(claimId, UUID.randomUUID()); + } catch (NumberFormatException e) { + if (file.getName().equalsIgnoreCase("_nextClaimID")) { + // GP keeps track of next claim ID in this file so we can safely ignore the exception + continue; + } + e.printStackTrace(); + continue; + } + } + } + migrateClaims(world, files, true); + migrateClaims(world, files, false); + GriefDefenderPlugin.getInstance().getLogger().info("Finished GriefPrevention data migration for world '" + world.getName() + "'." + + " Migrated a total of " + count + " claims."); + } + if (Files.exists(gpFlags)) { + migrateGpFlags(world); + } + } + + private static void migrateGpFlags(World world) { + YAMLConfigurationLoader flagsManager = YAMLConfigurationLoader.builder().setPath(gpFlags).build(); + ConfigurationNode root = null; + try { + root = flagsManager.load(); + } catch (IOException e1) { + e1.printStackTrace(); + return; + } + + int count = 0; + for (Entry<Object, ? extends ConfigurationNode> claims : root.getChildrenMap().entrySet()) { + Object key = claims.getKey(); + int bukkitClaimId = -1; + try { + bukkitClaimId = Integer.parseInt(key.toString()); + } catch (NumberFormatException e) { + e.printStackTrace(); + continue; + } + + final UUID claimUniqueId = idToUUID.get(bukkitClaimId); + if (claimUniqueId == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Could not locate migrated GD claim for id '" + bukkitClaimId + "'. Skipping..."); + continue; + } + + final Set<Context> contexts = new HashSet<>(); + final ClaimStorageData claimStorage = claimStorageMap.get(claimUniqueId); + if (claimStorage == null) { + // Different world + continue; + } + count++; + final UUID ownerUniqueId = claimStorage.getConfig().getOwnerUniqueId(); + GDPermissionUser claimOwner = null; + if (ownerUniqueId != null) { + claimOwner = PermissionHolderCache.getInstance().getOrCreateUser(ownerUniqueId); + } + contexts.add(new Context(ContextKeys.CLAIM, claimUniqueId.toString())); + ConfigurationNode flags = claims.getValue(); + GriefDefenderPlugin.getInstance().getLogger().info("Starting flag migration for legacy claim id '" + bukkitClaimId + "'..."); + for (Entry<Object, ? extends ConfigurationNode> flagEntry : flags.getChildrenMap().entrySet()){ + final String flag = (String) flagEntry.getKey(); + final ConfigurationNode paramNode = flagEntry.getValue(); + final String param = paramNode.getNode("params").getString(); + final boolean value = paramNode.getNode("value").getBoolean(); + if (!value) { + continue; + } + GriefDefenderPlugin.getInstance().getLogger().info("Migrating flag '" + flag + "'..."); + switch (flag) { + case FLAG_COMMAND_BLACKLIST : + case FLAG_COMMAND_WHITELIST : + // TODO + break; + case FLAG_ENTER_MESSAGE : + claimStorage.getConfig().setGreeting(LegacyComponentSerializer.legacy().deserialize(param, '�')); + claimStorage.getConfig().setRequiresSave(true); + claimStorage.save(); + break; + case FLAG_ENTER_COMMAND : + contexts.add(new Context(ContextKeys.FLAG, Flags.ENTER_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_COMMAND.getPermission(), param, contexts); + break; + case FLAG_ENTER_COMMAND_OWNER : + contexts.add(new Context(ContextKeys.FLAG, Flags.ENTER_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + if (claimOwner == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Could not locate owner legacy claim id '" + bukkitClaimId + "'. Skipping..."); + break; + } + PermissionUtil.getInstance().setOptionValue(claimOwner, Options.PLAYER_COMMAND.getPermission(), param, contexts); + break; + case FLAG_ENTER_COMMAND_MEMBERS : { + contexts.add(new Context(ContextKeys.FLAG, Flags.ENTER_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + List<UUID> members = new ArrayList<>(); + members.addAll(claimStorage.getConfig().getAccessors()); + members.addAll(claimStorage.getConfig().getBuilders()); + members.addAll(claimStorage.getConfig().getContainers()); + members.addAll(claimStorage.getConfig().getManagers()); + for (UUID memberUniqueId : members) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(memberUniqueId); + PermissionUtil.getInstance().setOptionValue(user, Options.PLAYER_COMMAND.getPermission(), param, contexts); + } + break; + } + case FLAG_ENTER_PLAYER_COMMAND : + contexts.add(new Context(ContextKeys.FLAG, Flags.ENTER_CLAIM.getName())); + contexts.add(new Context(ContextKeys.TARGET, "player")); + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_COMMAND.getPermission(), param, contexts); + break; + case FLAG_EXIT_COMMAND_MEMBERS : { + contexts.add(new Context(ContextKeys.FLAG, Flags.EXIT_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + List<UUID> members = new ArrayList<>(); + members.addAll(claimStorage.getConfig().getAccessors()); + members.addAll(claimStorage.getConfig().getBuilders()); + members.addAll(claimStorage.getConfig().getContainers()); + members.addAll(claimStorage.getConfig().getManagers()); + for (UUID memberUniqueId : members) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(memberUniqueId); + PermissionUtil.getInstance().setOptionValue(user, Options.PLAYER_COMMAND.getPermission(), param, contexts); + } + break; + } + case FLAG_EXIT_COMMAND_OWNER : + contexts.add(new Context(ContextKeys.FLAG, Flags.EXIT_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + if (claimOwner == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Could not locate owner legacy claim id '" + bukkitClaimId + "'. Skipping..."); + break; + } + PermissionUtil.getInstance().setOptionValue(claimOwner, Options.PLAYER_COMMAND.getPermission(), param, contexts); + break; + case FLAG_EXIT_COMMAND : + case FLAG_EXIT_PLAYER_COMMAND : + contexts.add(new Context(ContextKeys.FLAG, Flags.EXIT_CLAIM.getName())); + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_COMMAND.getPermission(), param, contexts); + break; + case FLAG_EXIT_MESSAGE : + claimStorage.getConfig().setFarewell(LegacyComponentSerializer.legacy().deserialize(param, '�')); + claimStorage.getConfig().setRequiresSave(true); + claimStorage.save(); + break; + case FLAG_HEALTH_REGEN : + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_HEALTH_REGEN.getPermission(), param, contexts); + break; + case FLAG_KEEP_INVENTORY : + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_KEEP_INVENTORY.getPermission(), "1", contexts); + break; + case FLAG_KEEP_LEVEL : + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_KEEP_LEVEL.getPermission(), "1", contexts); + break; + case FLAG_NO_CHORUS_FRUIT : + contexts.add(new Context(ContextKeys.TARGET, "chorus_fruit")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ITEM_USE.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_ENDER_PEARL : + contexts.add(new Context(ContextKeys.TARGET, "ender_pearl")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.INTERACT_ITEM_SECONDARY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_ENTER : + contexts.add(new Context(ContextKeys.TARGET, "player")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTER_CLAIM.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_ENTER_PLAYER : + // TODO + break; + case FLAG_NO_EXPIRATION : + claimStorage.getConfig().setExpiration(false); + claimStorage.getConfig().setRequiresSave(true); + claimStorage.save(); + break; + case FLAG_NO_EXPLOSION_DAMAGE : + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.EXPLOSION_BLOCK.getPermission(), Tristate.FALSE, contexts); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.EXPLOSION_ENTITY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_FALL_DAMAGE : + contexts.add(new Context(ContextKeys.SOURCE, "fall")); + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTITY_DAMAGE.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_FIRE_DAMAGE : + contexts.add(new Context(ContextKeys.SOURCE, "fire")); + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTITY_DAMAGE.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_FIRE_SPREAD : + contexts.add(new Context(ContextKeys.SOURCE, "fire")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.BLOCK_SPREAD.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_FLIGHT : + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_DENY_FLIGHT.getPermission(), "true", contexts); + break; + case FLAG_NO_FLUID_FLOW : + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.LIQUID_FLOW.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_GROWTH : + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.BLOCK_GROW.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_HUNGER : + PermissionUtil.getInstance().setOptionValue(DEFAULT_HOLDER, Options.PLAYER_DENY_HUNGER.getPermission(), "true", contexts); + break; + case FLAG_NO_ICE_FORM : + contexts.add(TARGET_ICE_FORM); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.BLOCK_MODIFY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_ITEM_DROP : + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ITEM_DROP.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_ITEM_PICKUP : + contexts.add(TARGET_PLAYER); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ITEM_PICKUP.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_LEAF_DECAY : + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.LEAF_DECAY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_MOB_DAMAGE : + contexts.add(SOURCE_PLAYER); + contexts.add(new Context(ContextKeys.TARGET, "#monster")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTITY_DAMAGE.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_MOB_SPAWNS : + contexts.add(new Context(ContextKeys.TARGET, "#monster")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTITY_SPAWN.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_PLAYER_DAMAGE : + contexts.add(new Context(ContextKeys.TARGET, "player")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.ENTITY_DAMAGE.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_SNOW_FORM : + contexts.add(TARGET_SNOW_LAYER); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.BLOCK_MODIFY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_VEHICLE : + contexts.add(new Context(ContextKeys.TARGET, "#vehicle")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.INTERACT_ENTITY_SECONDARY.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_NO_VINE_GROWTH : + contexts.add(new Context(ContextKeys.SOURCE, "vine")); + PermissionUtil.getInstance().setPermissionValue(DEFAULT_HOLDER, Flags.BLOCK_GROW.getPermission(), Tristate.FALSE, contexts); + break; + case FLAG_OWNER_FLY : { + if (claimOwner == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Could not locate owner legacy claim id '" + bukkitClaimId + "'. Skipping..."); + break; + } + PermissionUtil.getInstance().setOptionValue(claimOwner, Options.PLAYER_DENY_FLIGHT.getPermission(), "1", contexts); + break; + } + case FLAG_OWNER_MEMBER_FLY : { + List<UUID> members = new ArrayList<>(); + members.addAll(claimStorage.getConfig().getAccessors()); + members.addAll(claimStorage.getConfig().getBuilders()); + members.addAll(claimStorage.getConfig().getContainers()); + members.addAll(claimStorage.getConfig().getManagers()); + for (UUID memberUniqueId : members) { + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(memberUniqueId); + PermissionUtil.getInstance().setOptionValue(user, Options.PLAYER_DENY_FLIGHT.getPermission(), "1", contexts); + } + break; + } + + case FLAG_RESPAWN_LOCATION : + // TODO + break; + + // NOT CURRENTLY SUPPORTED + case FLAG_CHANGE_BIOME : + case FLAG_INFINITE_ARROWS : + case FLAG_NETHER_PORTAL_CONSOLE_COMMAND : + case FLAG_NETHER_PORTAL_PLAYER_COMMAND : + case FLAG_NO_COMBAT_LOOT : + case FLAG_NO_ITEM_DAMAGE : + case FLAG_NO_LOOT_PROTECTION : + case FLAG_NO_MOB_SPAWNS_TYPE : + case FLAG_NO_OPEN_DOORS : + case FLAG_NO_PET_DAMAGE : + case FLAG_NO_WEATHER_CHANGE : + case FLAG_PLAYER_GAMEMODE : + case FLAG_PLAYER_TIME : + case FLAG_PLAYER_WEATHER : + case FLAG_SPLEEF_ARENA : + case FLAG_TRAPPED_DESTINATION : + GriefDefenderPlugin.getInstance().getLogger().info("Flag '" + flag + "' not currently supported! Skipping..."); + break; + } + } + + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated GPFlags for claim id '" + bukkitClaimId + "' to '" + claimUniqueId + "'"); + } + GriefDefenderPlugin.getInstance().getLogger().info("Finished GPFlags data migration for world '" + world.getName() + "'." + + " Migrated a total of " + count + " claims."); + } + + private static void migratePlayerData(World world) { + if (gpBukkitPlayerDataMigrated.exists()) { + return; + } + + final Path path = Paths.get("plugins", "GriefPreventionData", "PlayerData"); + if (!path.toFile().exists()) { + return; + } + + File[] files = path.toFile().listFiles(); + if (files != null) { + GriefDefenderPlugin.getInstance().getLogger().info("Migrating " + files.length + " player data files..."); + for (int i = 0; i < files.length; i++) { + final File file = files[i]; + GriefDefenderPlugin.getInstance().getLogger().info("Migrating playerdata " + file.getName() + "..."); + UUID uuid = null; + try { + uuid = UUID.fromString(file.getName().replaceFirst("[.][^.]+$", "")); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + continue; + } + List<String> lines; + try { + lines = Files.readAllLines(file.toPath()); + } catch (IOException e) { + e.printStackTrace(); + continue; + } + if (lines.size() < 3) { + continue; + } + try { + final int accruedBlocks = Integer.parseInt(lines.get(1)); + final int bonusBlocks = Integer.parseInt(lines.get(2)); + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(world, uuid); + // Set directly in storage as subject data has not been initialized + playerData.getStorageData().getConfig().setAccruedClaimBlocks(accruedBlocks); + playerData.getStorageData().getConfig().setBonusClaimBlocks(bonusBlocks); + playerData.saveAllData(); + } catch (NumberFormatException e) { + e.printStackTrace(); + continue; + } + } + } + + try { + Files.createFile(gpBukkitPlayerDataMigrated.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void migrateClaims(World world, File[] files, boolean parentsOnly) { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + createClaim(world, file, parentsOnly); + } + } + } + + private static void createClaim(World world, File file, boolean parentsOnly) { + int claimId; + try { + claimId = Integer.parseInt(file.getName().replaceFirst("[.][^.]+$", "")); + } catch (NumberFormatException e) { + return; + } + final UUID claimUniqueId = idToUUID.get(claimId); + if (claimUniqueId == null) { + return; + } + YAMLConfigurationLoader regionManager = YAMLConfigurationLoader.builder().setPath(file.toPath()).build(); + ConfigurationNode region = null; + try { + region = regionManager.load(); + } catch (IOException e1) { + e1.printStackTrace(); + return; + } + if (parentsOnly && region.getChildrenMap().get("Parent Claim ID").getInt() != -1) { + return; + } + + final ConfigurationNode lesserNode = region.getChildrenMap().get("Lesser Boundary Corner"); + if (lesserNode.isVirtual()) { + return; + } + final String claimWorldName = getWorldName(lesserNode.getValue().toString()); + if (lesserNode != null && !world.getName().equalsIgnoreCase(claimWorldName)) { + return; + } + Vector3i lesserBoundaryCorner = null; + Vector3i greaterBoundaryCorner = null; + List<UUID> builders = new ArrayList<>(); + List<UUID> containers = new ArrayList<>(); + List<UUID> accessors = new ArrayList<>(); + List<UUID> managers = new ArrayList<>(); + UUID parentClaimUniqueId = null; + UUID ownerUniqueId = null; + boolean inherit = true; + for (Entry<Object, ? extends ConfigurationNode> mapEntry : region.getChildrenMap().entrySet()){ + Object key = mapEntry.getKey(); + ConfigurationNode value = mapEntry.getValue(); + if (key.equals("Lesser Boundary Corner")) { + lesserBoundaryCorner = classicPosFromString(value.getString(), true); + } else if (key.equals("Greater Boundary Corner")) { + greaterBoundaryCorner = classicPosFromString(value.getString(), false); + } else if (key.equals("Owner")) { + final String owner = value.getString(); + if (owner != null && !owner.isEmpty()) { + try { + ownerUniqueId = UUID.fromString(value.getString()); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + } else if (key.equals("Builders")) { + for (String id : (List<String>) value.getValue()) { + if (id.equals("public")) { + builders.add(GriefDefenderPlugin.PUBLIC_UUID); + } else { + try { + final UUID uuid = UUID.fromString(id); + builders.add(uuid); + } catch (IllegalArgumentException e) { + + } + } + } + } else if (key.equals("Containers")) { + for (String id : (List<String>) value.getValue()) { + if (id.equals("public")) { + containers.add(GriefDefenderPlugin.PUBLIC_UUID); + } else { + try { + final UUID uuid = UUID.fromString(id); + containers.add(uuid); + } catch (IllegalArgumentException e) { + + } + } + } + } else if (key.equals("Accessors")) { + for (String id : (List<String>) value.getValue()) { + if (id.equals("public")) { + accessors.add(GriefDefenderPlugin.PUBLIC_UUID); + } else { + try { + final UUID uuid = UUID.fromString(id); + accessors.add(uuid); + } catch (IllegalArgumentException e) { + + } + } + } + } else if (key.equals("Managers")) { + for (String id : (List<String>) value.getValue()) { + if (id.equals("public")) { + managers.add(GriefDefenderPlugin.PUBLIC_UUID); + } else { + try { + final UUID uuid = UUID.fromString(id); + managers.add(uuid); + } catch (IllegalArgumentException e) { + + } + } + } + } else if (key.equals("Parent Claim ID")) { + if (value.getInt() != -1) { + parentClaimUniqueId = idToUUID.get(value.getInt()); + if (parentClaimUniqueId == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Detected corrupted subdivision claim file '" + file + "' with parent id " + value.getInt() + " that does NOT exist. Skipping..."); + return; + } + } + } else if (key.equals("inheritNothing")) { + inherit = !value.getBoolean(); + } + } + if (lesserBoundaryCorner == null) { + return; + } + + ClaimType type = null; + if (parentClaimUniqueId != null) { + type = ClaimTypes.SUBDIVISION; + } else if (ownerUniqueId == null) { + type = ClaimTypes.ADMIN; + } else { + type = ClaimTypes.BASIC; + } + Path claimDataFolderPath = null; + if (parentClaimUniqueId != null) { + final ClaimStorageData claimStorage = claimStorageMap.get(parentClaimUniqueId); + claimDataFolderPath = claimStorage.filePath.getParent().resolve(type.getName().toLowerCase()); + if (ownerUniqueId == null) { + ownerUniqueId = claimStorage.getConfig().getOwnerUniqueId(); + } + } else { + claimDataFolderPath = BaseStorage.worldConfigMap.get(world.getUniqueId()).getPath().getParent().resolve("ClaimData").resolve(type.getName().toLowerCase()); + } + Path claimFilePath = claimDataFolderPath.resolve(claimUniqueId.toString()); + if (!Files.exists(claimFilePath)) { + claimFilePath.toFile().getParentFile().mkdirs(); + try { + Files.createFile(claimFilePath); + } catch (IOException e) { + e.printStackTrace(); + return; + } + } + + ClaimStorageData claimStorage = new ClaimStorageData(claimFilePath, world.getUniqueId()); + claimStorageMap.put(claimUniqueId, claimStorage); + ClaimDataConfig claimDataConfig = claimStorage.getConfig(); + if (ownerUniqueId != null) { + claimDataConfig.setOwnerUniqueId(ownerUniqueId); + } + + claimDataConfig.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(lesserBoundaryCorner)); + claimDataConfig.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(greaterBoundaryCorner)); + claimDataConfig.setBuilders(builders); + claimDataConfig.setContainers(containers); + claimDataConfig.setAccessors(accessors); + claimDataConfig.setManagers(managers); + claimDataConfig.setInheritParent(inherit); + claimDataConfig.setDateLastActive(Instant.now()); + claimDataConfig.setParent(parentClaimUniqueId); + claimDataConfig.setWorldUniqueId(world.getUniqueId()); + claimDataConfig.setType(type); + claimDataConfig.setRequiresSave(true); + claimStorage.save(); + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated GriefPrevention claim file '" + file.getName() + "' to '" + claimFilePath + "'"); + count++; + } + + private static String getWorldName(String string) { + String[] elements = string.split(";"); + return elements[0]; + } + + private static Vector3i classicPosFromString(String string, boolean lesser) { + // split the input string on the space + String[] elements = string.split(";"); + + String worldName = elements[0]; + String xString = elements[1]; + String zString = elements[3]; + + // convert those numerical strings to integer values + int x = Integer.parseInt(xString); + int y = lesser ? 0 : 255; + int z = Integer.parseInt(zString); + + return new Vector3i(x, y, z); + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/migrator/GPSpongeMigrator.java b/sponge/src/main/java/com/griefdefender/migrator/GPSpongeMigrator.java new file mode 100644 index 0000000..1f20717 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/migrator/GPSpongeMigrator.java @@ -0,0 +1,308 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.migrator; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.registry.FlagRegistryModule; +import org.apache.commons.io.FileUtils; +import org.spongepowered.api.service.context.Context; +import org.spongepowered.api.service.permission.PermissionService; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.util.Tristate; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class GPSpongeMigrator { + + final String regex = "^([^\\.]+).([^\\.]+).([^\\.]+)"; + final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); + + private static GPSpongeMigrator instance; + public PermissionService permissionService = GriefDefenderPlugin.getInstance().permissionService; + final Path GP_GLOBAL_PLAYER_DATA_PATH = Paths.get("config", "griefprevention", "GlobalPlayerData"); + final Path GP_CLAIM_DATA_PATH = Paths.get("config", "griefprevention", "worlds"); + final Path GD_DATA_ROOT_PATH = Paths.get("config", "griefdefender"); + + public void migrateData() { + try { + FileUtils.copyDirectory(GP_GLOBAL_PLAYER_DATA_PATH.toFile(), GD_DATA_ROOT_PATH.resolve("GlobalPlayerData").toFile(), true); + FileUtils.copyDirectory(GP_CLAIM_DATA_PATH.toFile(), GD_DATA_ROOT_PATH.resolve("worlds").toFile(), true); + //FileUtils.copyFile(GP_GLOBAL_CONFIG.toFile(), GD_DATA_ROOT_PATH.resolve("global.conf").toFile()); + } catch (IOException e1) { + e1.printStackTrace(); + } + final File dataPath = GD_DATA_ROOT_PATH.resolve("worlds").toFile(); + final File[] files = dataPath.listFiles(); + + GriefDefenderPlugin.getInstance().executor.execute(() -> { + final CompletableFuture<Set<String>> future = permissionService.getGroupSubjects().getAllIdentifiers(); + future.thenAccept(groups -> { + int count = 0; + final int size = groups.size(); + List<Integer> printedSizes = new CopyOnWriteArrayList<>(); + for (String group : groups) { + count++; + final Subject groupSubject = permissionService.getGroupSubjects().getSubject(group).orElse(null); + final int current = count; + if (groupSubject == null) { + permissionService.getGroupSubjects().loadSubject(group).thenAccept(s -> { + migrateSubject(s, true); + final int printSize = (current*100/size); + if (!printedSizes.contains(printSize)) { + GriefDefenderPlugin.getInstance().getLogger().info("Performing group permission migration: " + printSize + "%"); + printedSizes.add(printSize); + } + }); + } else { + migrateSubject(groupSubject, true); + final int printSize = (current*100/size); + if (!printedSizes.contains(printSize)) { + GriefDefenderPlugin.getInstance().getLogger().info("Performing group permission migration: " + printSize + "%"); + printedSizes.add(printSize); + } + } + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + final CompletableFuture<Set<String>> futureUsers = permissionService.getUserSubjects().getAllIdentifiers(); + futureUsers.thenAccept(users -> { + int count = 0; + final int size = users.size(); + List<Integer> printedSizes = new CopyOnWriteArrayList<>(); + for (String user : users) { + count++; + final Subject userSubject = permissionService.getUserSubjects().getSubject(user).orElse(null); + final int current = count; + if (userSubject == null) { + permissionService.getUserSubjects().loadSubject(user).thenAccept(s -> { + migrateSubject(s, false); + final int printSize = (current*100/size); + if (!printedSizes.contains(printSize)) { + GriefDefenderPlugin.getInstance().getLogger().info("Performing user permission migration: " + printSize + "%"); + printedSizes.add(printSize); + } + }); + } else { + migrateSubject(userSubject, false); + final int printSize = (current*100/size); + if (!printedSizes.contains(printSize) && printSize != 100) { + GriefDefenderPlugin.getInstance().getLogger().info("Performing user permission migration: " + printSize + "%"); + printedSizes.add(printSize); + } + } + try { + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + // migrate default user + migrateSubject(GriefDefenderPlugin.getInstance().permissionService.getDefaults(), false); + GriefDefenderPlugin.getInstance().getLogger().info("Performing user permission migration: 100%"); + }); + }); + } + + private void migrateFolderData(File[] files) { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isDirectory()) { + migrateFolderData(file.listFiles()); + } + } + } + + public Consumer<Subject> migrateSubjectPermissions(boolean isGroup) { + return subject -> { + migrateSubject(subject, isGroup); + }; + } + + public void migrateSubject(Subject subject, boolean isGroup) { + boolean migrated = false; + try { + for (Map.Entry<Set<Context>, Map<String, Boolean>> mapEntry : subject.getSubjectData().getAllPermissions().entrySet()) { + final Set<Context> originalContexts = mapEntry.getKey(); + Iterator<Map.Entry<String, Boolean>> iterator = mapEntry.getValue().entrySet().iterator(); + Set<Context> gdContexts = new HashSet<>(); + for (Context context : originalContexts) { + if (context.getKey().equalsIgnoreCase("gp_claim_overrides")) { + gdContexts.add(new Context("gd_claim_override", context.getValue())); + } else if (context.getKey().equalsIgnoreCase("gp_claim_defaults")) { + gdContexts.add(new Context("gd_claim_default", context.getValue())); + } else if (context.getKey().equalsIgnoreCase("gp_claim")) { + gdContexts.add(new Context("gd_claim", context.getValue())); + } else { + gdContexts.add(context); + } + } + while (iterator.hasNext()) { + final Map.Entry<String, Boolean> entry = iterator.next(); + final String originalPermission = entry.getKey(); + if (!originalPermission.startsWith("griefprevention")) { + continue; + } + final String currentPermission = originalPermission.replace("griefprevention", "griefdefender").replace("fire-spread", "block-spread"); + final Matcher matcher = pattern.matcher(currentPermission); + String flagBase = ""; + if (matcher.find()) { + flagBase = matcher.group(0); + } + final Flag flag = FlagRegistryModule.getInstance().getById(flagBase).orElse(null); + if (flag == null) { + GriefDefenderPlugin.getInstance().getLogger().info("Detected legacy permission '" + originalPermission + "' on subject " + subject.getFriendlyIdentifier().orElse(subject.getIdentifier()) + "'. Migrating..."); + subject.getSubjectData().setPermission(originalContexts, originalPermission, Tristate.UNDEFINED); + GriefDefenderPlugin.getInstance().getLogger().info("Removed legacy permission '" + originalPermission + "'."); + subject.getSubjectData().setPermission(originalContexts, currentPermission, Tristate.fromBoolean(entry.getValue())); + GriefDefenderPlugin.getInstance().getLogger().info("Set new permission '" + currentPermission + "' with contexts " + originalContexts); + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated permission " + currentPermission + " to " + currentPermission + " with contexts " + originalContexts); + } else { + GriefDefenderPlugin.getInstance().getLogger().info("Detected legacy flag permission '" + originalPermission + "' on subject " + subject.getFriendlyIdentifier().orElse(subject.getIdentifier()) + "'. Migrating..."); + subject.getSubjectData().setPermission(originalContexts, originalPermission, Tristate.UNDEFINED); + GriefDefenderPlugin.getInstance().getLogger().info("Removed legacy flag permission '" + originalPermission + "'."); + Set<Context> newContextSet = new HashSet<>(gdContexts); + applyContexts(flagBase, currentPermission, newContextSet); + subject.getSubjectData().setPermission(newContextSet, flag.getPermission(), Tristate.fromBoolean(entry.getValue())); + GriefDefenderPlugin.getInstance().getLogger().info("Set new flag permission '" + flag.getPermission() + "' with contexts " + newContextSet); + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated flag permission " + currentPermission + " to " + flag.getPermission() + " with contexts " + newContextSet); + } + migrated = true; + } + } + if (migrated) { + GriefDefenderPlugin.getInstance().getLogger().info("Finished migration of subject '" + subject.getIdentifier() + "'\n"); + } + if (isGroup) { + permissionService.getGroupSubjects().suggestUnload(subject.getIdentifier()); + } else { + permissionService.getUserSubjects().suggestUnload(subject.getIdentifier()); + } + } catch(Throwable t) { + t.printStackTrace(); + } + } + + private void applyContexts(String flagBase, String currentPermission, Set<Context> contexts) { + final int permLength = currentPermission.split("\\.").length; + if (permLength == 3) { + // Only contains flag + return; + } + + String newPermission = currentPermission.replace(flagBase + ".", ""); + final String[] parts = newPermission.split("\\.source\\."); + String targetPart = parts[0]; + String sourcePart = parts.length > 1 ? parts[1] : null; + if (sourcePart != null) { + // handle source + String sourceParts[] = sourcePart.split("\\."); + String sourceId = sourceParts[0]; + if (sourceParts.length > 1) { + String value = sourceParts[1]; + int index = 2; + while (index < sourceParts.length) { + value += "." + sourceParts[index]; + index++; + } + if (value.equals("animal")) { + sourceId = "#animal"; + } else if (value.equals("monster")) { + sourceId = "#monster"; + } else { + if (value.contains("animal.")) { + value = value.replace("animal.", ""); + } else if (value.contains("monster.")) { + value = value.replace("monster.", ""); + } + sourceId += ":" + value; + } + } else { + sourceId += ":any"; + } + if (currentPermission.contains("interact") && GriefDefenderPlugin.ITEM_IDS.contains(sourceId)) { + contexts.add(new Context("used_item", sourceId)); + GriefDefenderPlugin.getInstance().getLogger().info("Created new used_item context for '" + sourceId + "'."); + } else { + contexts.add(new Context("source", sourceId)); + GriefDefenderPlugin.getInstance().getLogger().info("Created new source context for '" + sourceId + "'."); + } + } + + // Handle target + String targetParts[] = targetPart.split("\\."); + String targetId = targetParts[0]; + if (targetParts.length > 1) { + String value = targetParts[1]; + int index = 2; + while (index < targetParts.length) { + value += "." + targetParts[index]; + index++; + } + if (value.equals("animal")) { + targetId = "#animal"; + } else if (value.equals("monster")) { + targetId = "#monster"; + } else { + if (value.contains("animal.")) { + value = value.replace("animal.", ""); + } else if (value.contains("monster.")) { + value = value.replace("monster.", ""); + } + targetId += ":" + value; + } + } else { + targetId += ":any"; + } + contexts.add(new Context(ContextKeys.TARGET, targetId)); + GriefDefenderPlugin.getInstance().getLogger().info("Created new target context for '" + targetId + "'."); + } + + public static GPSpongeMigrator getInstance() { + return instance; + } + + static { + instance = new GPSpongeMigrator(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/migrator/RedProtectMigrator.java b/sponge/src/main/java/com/griefdefender/migrator/RedProtectMigrator.java new file mode 100644 index 0000000..3e177bf --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/migrator/RedProtectMigrator.java @@ -0,0 +1,193 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.migrator; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.configuration.ClaimDataConfig; +import com.griefdefender.configuration.ClaimStorageData; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.util.PermissionUtil; +import com.griefdefender.util.PlayerUtil; +import net.kyori.text.TextComponent; +import ninja.leaping.configurate.commented.CommentedConfigurationNode; +import ninja.leaping.configurate.hocon.HoconConfigurationLoader; +import ninja.leaping.configurate.loader.ConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RedProtectMigrator { + + private static final String USERNAME_PATTERN = "[a-zA-Z0-9_]+"; + + public static void migrate(World world, Path redProtectFilePath, Path gpClaimDataPath) throws FileNotFoundException, ClassNotFoundException { + if (!GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.redProtectMigrator) { + return; + } + + int count = 0; + try { + GriefDefenderPlugin.getInstance().getLogger().info("Starting RedProtect region data migration for world " + world.getProperties().getWorldName() + "..."); + ConfigurationLoader<CommentedConfigurationNode> regionManager = HoconConfigurationLoader.builder().setPath(redProtectFilePath).build(); + CommentedConfigurationNode region = regionManager.load(); + GriefDefenderPlugin.getInstance().getLogger().info("Scanning RedProtect regions in world data file '" + redProtectFilePath + "'..."); + for (Object key:region.getChildrenMap().keySet()){ + String rname = key.toString(); + if (!region.getNode(rname).hasMapChildren()){ + continue; + } + int maxX = region.getNode(rname,"maxX").getInt(); + int maxY = region.getNode(rname,"maxY").getInt(255); + int maxZ = region.getNode(rname,"maxZ").getInt(); + int minX = region.getNode(rname,"minX").getInt(); + int minY = region.getNode(rname,"minY").getInt(0); + int minZ = region.getNode(rname,"minZ").getInt(); + List<String> owners = new ArrayList<String>(); + owners.addAll(region.getNode(rname,"owners").getList(TypeToken.of(String.class))); + + List<String> members = new ArrayList<String>(); + members.addAll(region.getNode(rname,"members").getList(TypeToken.of(String.class))); + + String creator = region.getNode(rname,"creator").getString(); + String welcome = region.getNode(rname,"welcome").getString(); + + // create GP claim data file + GriefDefenderPlugin.getInstance().getLogger().info("Migrating RedProtect region data '" + rname + "'..."); + UUID ownerUniqueId = null; + if (validate(creator)) { + try { + // check cache first + ownerUniqueId = PermissionUtil.getInstance().lookupUserUniqueId(creator); + if (ownerUniqueId == null) { + ownerUniqueId = UUID.fromString(getUUID(creator)); + } + } catch (Throwable e) { + // assume admin claim + } + } + + UUID claimUniqueId = UUID.randomUUID(); + Location<World> lesserBoundaryCorner = new Location<>(world, minX, minY, minZ); + Location<World> greaterBoundaryCorner = new Location<>(world, maxX, maxY, maxZ); + Path claimFilePath = gpClaimDataPath.resolve(claimUniqueId.toString()); + if (!Files.exists(claimFilePath)) { + Files.createFile(claimFilePath); + } + + ClaimStorageData claimStorage = new ClaimStorageData(claimFilePath, world.getUniqueId()); + ClaimDataConfig claimDataConfig = claimStorage.getConfig(); + claimDataConfig.setName(TextComponent.of(rname)); + claimDataConfig.setWorldUniqueId(world.getUniqueId()); + claimDataConfig.setOwnerUniqueId(ownerUniqueId); + claimDataConfig.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(lesserBoundaryCorner)); + claimDataConfig.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(greaterBoundaryCorner)); + claimDataConfig.setDateLastActive(Instant.now()); + claimDataConfig.setType(ownerUniqueId == null ? ClaimTypes.ADMIN : ClaimTypes.BASIC); + if (!welcome.equals("")) { + claimDataConfig.setGreeting(TextComponent.of(welcome)); + } + List<String> rpUsers = new ArrayList<>(owners); + rpUsers.addAll(members); + List<UUID> builders = claimDataConfig.getBuilders(); + for (String builder : rpUsers) { + if (!validate(builder)) { + continue; + } + + UUID builderUniqueId = null; + try { + builderUniqueId = PermissionUtil.getInstance().lookupUserUniqueId(creator); + if (builderUniqueId == null) { + builderUniqueId = UUID.fromString(getUUID(builder)); + } + } catch (Throwable e) { + GriefDefenderPlugin.getInstance().getLogger().error("Could not locate a valid UUID for user '" + builder + "' in region '" + rname + + "'. Skipping..."); + continue; + } + if (!builders.contains(builderUniqueId) && ownerUniqueId != null && !builderUniqueId.equals(ownerUniqueId)) { + builders.add(builderUniqueId); + } + } + + claimDataConfig.setRequiresSave(true); + claimStorage.save(); + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated RedProtect region data '" + rname + "' to '" + claimFilePath + "'"); + count++; + } + GriefDefenderPlugin.getInstance().getLogger().info("Finished RedProtect region data migration for world '" + world.getProperties().getWorldName() + "'." + + " Migrated a total of " + count + " regions."); + } catch (IOException e) { + e.printStackTrace(); + } catch (ObjectMappingException e) { + e.printStackTrace(); + } + } + + private static boolean validate(final String username){ + Matcher matcher = Pattern.compile(USERNAME_PATTERN).matcher(username); + return matcher.matches(); + } + + // Below taken from https://github.com/FabioZumbi12/Sponge-Redprotect-19/blob/master/src/main/java/br/net/fabiozumbi12/redprotect/MojangUUIDs.java + public static String getUUID(String player) { + try { + URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + player); + BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream())); + String line = in.readLine(); + if (line == null){ + return null; + } + // JSONObject jsonProfile = (JSONObject) new JSONParser().parse(line); + String name = "";//(String) jsonProfile.get("id"); + return toUUID(name); + } catch (Exception ex) { + ex.printStackTrace(); + } + return null; + } + + private static String toUUID(String uuid){ + return uuid.substring(0, 8) + "-" + uuid.substring(8, 12) + "-" + + uuid.substring(12, 16) + "-" + uuid.substring(16, 20) + + "-" + uuid.substring(20, 32); + } +} diff --git a/sponge/src/main/java/com/griefdefender/migrator/WorldGuardMigrator.java b/sponge/src/main/java/com/griefdefender/migrator/WorldGuardMigrator.java new file mode 100644 index 0000000..ac8538c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/migrator/WorldGuardMigrator.java @@ -0,0 +1,793 @@ +/* + * This file is part of GriefPrevention, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.migrator; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.ClaimDataConfig; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; + +import net.kyori.text.TextComponent; +import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.loader.ConfigurationLoader; +import ninja.leaping.configurate.objectmapping.ObjectMappingException; +import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; +import org.spongepowered.api.world.DimensionType; +import org.spongepowered.api.world.World; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.ArrayList; +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; + +public class WorldGuardMigrator { + + static final Context SOURCE_PLAYER = new Context("source", "player"); + static final Context SOURCE_TNT = new Context("source", "tnt"); + static final Context SOURCE_CREEPER = new Context("source", "creeper"); + static final Context SOURCE_ENDERDRAGON = new Context("source", "enderdragon"); + static final Context SOURCE_GHAST = new Context("source", "ghast"); + static final Context SOURCE_ENDERMAN = new Context("source", "enderman"); + static final Context SOURCE_SNOWMAN = new Context("source", "snowman"); + static final Context SOURCE_MONSTER = new Context("source", "monster"); + static final Context SOURCE_WITHER = new Context("source", "wither"); + static final Context SOURCE_LAVA = new Context("source", "flowing_lava"); + static final Context SOURCE_WATER = new Context("source", "flowing_water"); + static final Context SOURCE_LIGHTNING_BOLT = new Context("source", "lightning_bolt"); + static final Context SOURCE_FALL = new Context("source", "fall"); + static final Context SOURCE_FIREWORKS = new Context("source", "fireworks"); + + // Block States + static final Context STATE_FARMLAND_DRY = new Context("state", "moisture:0"); + + // Targets + static final Context TARGET_BED = new Context("target", "bed"); + static final Context TARGET_BOAT = new Context("target", "boat"); + static final Context TARGET_CHEST = new Context("target", "chest"); + static final Context TARGET_CHORUS_FRUIT = new Context("target", "chorus_fruit"); + static final Context TARGET_ENDERPEARL = new Context("target", "enderpearl"); + static final Context TARGET_FARMLAND = new Context("target", "farmland"); + static final Context TARGET_FLINTANDSTEEL = new Context("target", "flint_and_steel"); + static final Context TARGET_GRASS= new Context("target", "grass"); + static final Context TARGET_ITEM_FRAME = new Context("target", "item_frame"); + static final Context TARGET_MINECART = new Context("target", "minecart"); + static final Context TARGET_PAINTING = new Context("target", "painting"); + static final Context TARGET_PLAYER = new Context("target", "player"); + static final Context TARGET_ICE_FORM = new Context("target", "ice"); + static final Context TARGET_ICE_MELT = new Context("target", "water"); + static final Context TARGET_SNOW_LAYER = new Context("target", "snow_layer"); + static final Context TARGET_TURTLE_EGG = new Context("target", "turtle_egg"); + static final Context TARGET_VINE = new Context("target", "vine"); + static final Context TARGET_XP_ORB = new Context("target", "xp_orb"); + static final Context TARGET_TYPE_ANIMAL = new Context("target", "#animal"); + static final Context TARGET_TYPE_CROP = new Context("target", "#crop"); + static final Context TARGET_TYPE_MONSTER = new Context("target", "#monster"); + static final Context TARGET_TYPE_MUSHROOM = new Context("target", "#mushroom"); + static final Context TARGET_TYPE_VEHICLE = new Context("target", "#vehicle"); + static final GDPermissionManager PERMISSION_MANAGER = GDPermissionManager.getInstance(); + + public static void migrate(World world) throws FileNotFoundException, ClassNotFoundException { + if (!GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.worldGuardMigrator) { + return; + } + + final DimensionType dimType = world.getDimension().getType(); + final String worldName = world.getName().toLowerCase(); + final String dimName = dimType.getName().toLowerCase(); + final Path path = Paths.get("plugins", "WorldGuard", "worlds", worldName, "regions.yml"); + List<GDClaim> tempClaimList = new ArrayList<>(); + int count = 0; + try { + GriefDefenderPlugin.getInstance().getLogger().info("Starting WorldGuard region data migration for world " + worldName + "..."); + ConfigurationLoader<ConfigurationNode> regionManager = YAMLConfigurationLoader.builder().setPath(path).build(); + ConfigurationNode region = regionManager.load().getNode("regions"); + GriefDefenderPlugin.getInstance().getLogger().info("Scanning WorldGuard regions in world data file '" + path + "'..."); + for (Object key:region.getChildrenMap().keySet()){ + String rname = key.toString(); + + boolean isWildernessClaim = false; + if (rname.equalsIgnoreCase("__global__")) { + isWildernessClaim = true; + } + if (!region.getNode(rname).hasMapChildren()){ + continue; + } + if (!isWildernessClaim && !region.getNode(rname, "type").getString().equals("cuboid")) { + GriefDefenderPlugin.getInstance().getLogger().info("Unable to migrate region '" + rname + "' as it is not a cuboid. Skipping..."); + continue; + } + + final Map<Object, ? extends ConfigurationNode> minMap = region.getNode(rname, "min").getChildrenMap(); + final Map<Object, ? extends ConfigurationNode> maxMap = region.getNode(rname, "max").getChildrenMap(); + final Map<Object, ? extends ConfigurationNode> flagsMap = region.getNode(rname, "flags").getChildrenMap(); + final List<UUID> membersList = region.getNode(rname, "members").getNode("unique-ids").getList(TypeToken.of(UUID.class)); + final List<UUID> ownersList = region.getNode(rname, "owners").getNode("unique-ids").getList(TypeToken.of(UUID.class)); + + List<UUID> managers = new ArrayList<UUID>(); + UUID creator = null; + for (UUID uuid : ownersList) { + if (managers.isEmpty()) { + creator = uuid; + } else { + managers.add(uuid); + } + } + + // create GP claim data file + GriefDefenderPlugin.getInstance().getLogger().info("Migrating WorldGuard region data '" + rname + "'..."); + GDPermissionUser owner = null; + if (!isWildernessClaim) { + try { + // check cache first + owner = PermissionHolderCache.getInstance().getOrCreateUser(creator); + } catch (Throwable e) { + // assume admin claim + } + } + + UUID claimUniqueId = isWildernessClaim ? world.getUniqueId() : UUID.randomUUID(); + ClaimType type = isWildernessClaim ? ClaimTypes.WILDERNESS : owner == null ? ClaimTypes.ADMIN : ClaimTypes.BASIC; + + ClaimDataConfig claimDataConfig = null; + GDClaim newClaim = null; + if (isWildernessClaim) { + newClaim = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()).getWildernessClaim(); + } else { + final Vector3i min = new Vector3i(minMap.get("x").getInt(), minMap.get("y").getInt(), minMap.get("z").getInt()); + final Vector3i max = new Vector3i(maxMap.get("x").getInt(), maxMap.get("y").getInt(), maxMap.get("z").getInt()); + final boolean cuboid = min.getY() == 0 && max.getY() == 256 ? true : false; + newClaim = new GDClaim(world, min, max, claimUniqueId, type, owner == null ? null : owner.getUniqueId(), cuboid); + final ClaimResult claimResult = newClaim.checkArea(false); + if (!claimResult.successful()) { + GriefDefenderPlugin.getInstance().getLogger().info("Could not migrate region '" + rname + "' due to reason: " + claimResult.getResultType()); + continue; + } + + boolean validClaim = true; + Claim parentClaim = null; + Claim childClaim = null; + if (type != ClaimTypes.WILDERNESS) { + for (GDClaim claim : tempClaimList) { + // Search for parent + final ClaimResult result = newClaim.findParent(claim); + if (result.successful()) { + final Claim parent = result.getClaim().get(); + if (parent.isSubdivision()) { + // avoid creating child claim under subdivision + // instead create admin claim + break; + } + if (parent.isBasicClaim() || parent.isAdminClaim()) { + parentClaim = parent; + if (type == ClaimTypes.BASIC) { + if (parent.isBasicClaim()) { + type = ClaimTypes.SUBDIVISION; + newClaim.setType(type); + } else { + type = ClaimTypes.BASIC; + newClaim.setType(type); + } + } + ((GDClaim) parent).children.add(newClaim); + newClaim.parent = (GDClaim) parent; + GriefDefenderPlugin.getInstance().getLogger().info("Found parent region '" + parent.getName().orElse(TextComponent.of("unknown")) + "'. Set current region '" + rname + "' as it's child."); + } else { + GriefDefenderPlugin.getInstance().getLogger().warn("Could not migrate region '" + rname + "' as it exceeds the maximum level supported by migrator. Skipping..."); + validClaim = false; + } + break; + } + // Search for child + if (claim.isInside(newClaim)) { + if (!claim.getParent().isPresent()) { + childClaim = claim; + claim.getClaimStorage().getConfig().setType(ClaimTypes.SUBDIVISION); + claim.getClaimStorage().getConfig().setParent(newClaim.getUniqueId()); + GriefDefenderPlugin.getInstance().getLogger().info("Found child region '" + claim.getName().orElse(TextComponent.of("unknown")) + "'. Set current region '" + rname + "' as it's parent."); + } + } + } + if (!validClaim) { + continue; + } + tempClaimList.add(newClaim); + } + + newClaim.initializeClaimData((GDClaim) parentClaim); + if (childClaim != null) { + // Migrate child to parent + ((GDClaim) newClaim).moveChildToParent(newClaim, (GDClaim) childClaim); + } + claimDataConfig = newClaim.getClaimStorage().getConfig(); + claimDataConfig.setName(TextComponent.of(rname)); + claimDataConfig.setWorldUniqueId(world.getUniqueId()); + if (owner != null) { + claimDataConfig.setOwnerUniqueId(owner.getUniqueId()); + } + claimDataConfig.setLesserBoundaryCorner(BlockUtil.getInstance().posToString(min)); + claimDataConfig.setGreaterBoundaryCorner(BlockUtil.getInstance().posToString(max)); + claimDataConfig.setCuboid(cuboid); + claimDataConfig.setDateLastActive(Instant.now()); + claimDataConfig.setType(type); + } + claimDataConfig = newClaim.getClaimStorage().getConfig(); + + for (UUID builder : membersList) { + GDPermissionUser builderUser = null; + try { + builderUser = PermissionHolderCache.getInstance().getOrCreateUser(builder); + } catch (Throwable e) { + GriefDefenderPlugin.getInstance().getLogger().warn("Could not locate a valid UUID for user '" + builder + "' in region '" + rname + + "'. Skipping..."); + continue; + } + if (!claimDataConfig.getBuilders().contains(builderUser.getUniqueId()) && owner != null && !builderUser.getUniqueId().equals(owner.getUniqueId())) { + claimDataConfig.getBuilders().add(builderUser.getUniqueId()); + } + } + + for (UUID manager : managers) { + GDPermissionUser managerUser = null; + try { + managerUser = PermissionHolderCache.getInstance().getOrCreateUser(manager); + } catch (Throwable e) { + GriefDefenderPlugin.getInstance().getLogger().warn("Could not locate a valid UUID for user '" + manager + "' in region '" + rname + + "'. Skipping..."); + continue; + } + if (!claimDataConfig.getManagers().contains(managerUser.getUniqueId()) && owner != null && !managerUser.getUniqueId().equals(owner.getUniqueId())) { + claimDataConfig.getManagers().add(managerUser.getUniqueId()); + } + } + final Set<Context> claimContextSet = new HashSet<>(); + claimContextSet.add(newClaim.getContext()); + + // migrate flags + for (Entry<Object, ? extends ConfigurationNode> mapEntry : flagsMap.entrySet()) { + if (!(mapEntry.getKey() instanceof String)) { + continue; + } + final String flag = (String) mapEntry.getKey(); + final Set<Context> contexts = new HashSet<>(claimContextSet); + ConfigurationNode valueNode = mapEntry.getValue(); + switch (flag) { + case "build": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_PRIMARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ENTITY_PRIMARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ENTITY_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.TRUE, contexts); + } + break; + case "interact": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_PRIMARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ENTITY_PRIMARY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ENTITY_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.TRUE, contexts); + } + break; + case "block-break": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + } + break; + case "block-place": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.TRUE, contexts); + } + break; + case "use": + if (valueNode.getString().equals("allow")) { + newClaim.addUserTrust(GriefDefenderPlugin.PUBLIC_UUID, TrustTypes.ACCESSOR); + } + break; + case "damage-animals": + contexts.add(TARGET_TYPE_ANIMAL); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "chest-access": + contexts.add(TARGET_CHEST); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "ride": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_RIDING, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_RIDING, Tristate.TRUE, contexts); + } + break; + case "pvp": + contexts.add(SOURCE_PLAYER); + contexts.add(TARGET_PLAYER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "sleep": + contexts.add(TARGET_BED); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "tnt": + contexts.add(SOURCE_TNT); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.TRUE, contexts); + } + break; + case "vehicle-place": + contexts.add(TARGET_TYPE_VEHICLE); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_BLOCK_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "lighter": + contexts.add(TARGET_FLINTANDSTEEL); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "block-trampling": + contexts.add(TARGET_FARMLAND); + contexts.add(TARGET_TURTLE_EGG); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.COLLIDE_BLOCK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.COLLIDE_BLOCK, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.COLLIDE_BLOCK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.COLLIDE_BLOCK, Tristate.TRUE, contexts); + } + break; + case "frosted-ice-form": + break; + case "creeper-explosion": + contexts.add(SOURCE_CREEPER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.TRUE, contexts); + } + break; + case "enderdragon-block-damage": + contexts.add(SOURCE_ENDERDRAGON); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "ghast-fireball": + contexts.add(SOURCE_GHAST); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "other-explosion": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_BLOCK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.EXPLOSION_ENTITY, Tristate.TRUE, contexts); + } + break; + case "fire-spread": + contexts.add(new Context(ContextKeys.SOURCE, "fire")); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_SPREAD, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_SPREAD, Tristate.TRUE, contexts); + } + break; + case "enderman-grief": + contexts.add(SOURCE_ENDERMAN); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "snowman-trail": + contexts.add(SOURCE_SNOWMAN); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "mob-damage": + contexts.add(TARGET_TYPE_MONSTER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "mob-spawning": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_SPAWN, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_SPAWN, Tristate.TRUE, contexts); + } + break; + case "deny-spawn": + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_SPAWN, Tristate.FALSE, contexts); + break; + case "entity-painting-destroy": + contexts.add(TARGET_PAINTING); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "entity-item-frame-destroy": + contexts.add(TARGET_ITEM_FRAME); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "wither-damage": + contexts.add(SOURCE_WITHER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "lava-fire": + contexts.add(SOURCE_LAVA); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_SPREAD, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_SPREAD, Tristate.TRUE, contexts); + } + break; + case "lightning": + contexts.add(SOURCE_LIGHTNING_BOLT); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "water-flow": + contexts.add(SOURCE_WATER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.LIQUID_FLOW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.LIQUID_FLOW, Tristate.TRUE, contexts); + } + break; + case "lava-flow": + contexts.add(SOURCE_LAVA); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.LIQUID_FLOW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.LIQUID_FLOW, Tristate.TRUE, contexts); + } + break; + case "snow-fall": + contexts.add(TARGET_SNOW_LAYER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_PLACE, Tristate.TRUE, contexts); + } + break; + case "snow-melt": + contexts.add(TARGET_SNOW_LAYER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_BREAK, Tristate.TRUE, contexts); + } + break; + case "ice-form": + contexts.add(TARGET_ICE_FORM); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "ice-melt": + contexts.add(TARGET_ICE_MELT); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "frosted-ice-melt": + break; + case "mushroom-growth": + contexts.add(TARGET_TYPE_MUSHROOM); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.TRUE, contexts); + } + break; + case "leaf-decay": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.LEAF_DECAY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.LEAF_DECAY, Tristate.TRUE, contexts); + } + break; + case "grass-growth": + contexts.add(TARGET_GRASS); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.TRUE, contexts); + } + break; + case "mycelium-spread": + break; + case "vine-growth": + contexts.add(TARGET_VINE); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.TRUE, contexts); + } + break; + case "crop-growth": + contexts.add(TARGET_TYPE_CROP); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_GROW, Tristate.TRUE, contexts); + } + break; + case "soil-dry": + contexts.add(STATE_FARMLAND_DRY); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.BLOCK_MODIFY, Tristate.TRUE, contexts); + } + break; + case "entry": + contexts.add(TARGET_PLAYER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTER_CLAIM, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTER_CLAIM, Tristate.TRUE, contexts); + } + break; + case "exit": + contexts.add(TARGET_PLAYER); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.EXIT_CLAIM, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.EXIT_CLAIM, Tristate.TRUE, contexts); + } + break; + // These can be handled via GD API + case "exit-override": + case "entry-deny-message": + case "exit-deny-message": + case "notify-enter": + case "notify-exit": + break; + case "greeting": + final String greeting = valueNode.getString(); + if (greeting != null && !greeting.equals("")) { + claimDataConfig.setGreeting(LegacyComponentSerializer.legacy().deserialize(greeting, '&')); + } + break; + case "farewell": + final String farewell = valueNode.getString(); + if (farewell != null && !farewell.equals("")) { + claimDataConfig.setFarewell(LegacyComponentSerializer.legacy().deserialize(farewell, '&')); + } + case "enderpearl": + contexts.add(TARGET_ENDERPEARL); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "chorus-fruit-teleport": + contexts.add(TARGET_CHORUS_FRUIT); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.INTERACT_ITEM_SECONDARY, Tristate.TRUE, contexts); + } + break; + case "teleport": + final String location = valueNode.getString(); + break; + case "spawn": + break; + case "item-pickup": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_PICKUP, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_PICKUP, Tristate.TRUE, contexts); + } + break; + case "item-drop": + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_DROP, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_DROP, Tristate.TRUE, contexts); + } + break; + case "exp-drop": + contexts.add(TARGET_XP_ORB); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_DROP, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ITEM_DROP, Tristate.TRUE, contexts); + } + break; + case "deny-message": + break; + case "invincible": + contexts.add(TARGET_PLAYER); + if (valueNode.getString().equals("allow")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } + break; + case "fall-damage": + contexts.add(SOURCE_FALL); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "firework-damage": + contexts.add(SOURCE_FIREWORKS); + if (valueNode.getString().equals("deny")) { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.FALSE, contexts); + } else { + PERMISSION_MANAGER.setFlagPermission(Flags.ENTITY_DAMAGE, Tristate.TRUE, contexts); + } + break; + case "game-mode": + case "time-lock": + case "weather-lock": + case "heal-delay": + case "heal-amount": + case "heal-min-health": + case "heal-max-health": + case "feed-delay": + case "feed-amount": + case "feed-min-hunger": + case "feed-max-hunger": + break; + case "blocked-cmds": + List<String> blocked = valueNode.getList(TypeToken.of(String.class)); + for (String cmd : blocked) { + final Context context = new Context(ContextKeys.TARGET, cmd); + contexts.add(context); + PERMISSION_MANAGER.setFlagPermission(Flags.COMMAND_EXECUTE, Tristate.FALSE, contexts); + contexts.remove(context); + } + break; + case "allowed-cmds": + List<String> allowed = valueNode.getList(TypeToken.of(String.class)); + for (String cmd : allowed) { + final Context context = new Context(ContextKeys.TARGET, cmd); + contexts.add(context); + PERMISSION_MANAGER.setFlagPermission(Flags.COMMAND_EXECUTE, Tristate.TRUE, contexts); + contexts.remove(context); + } + } + } + + claimDataConfig.setRequiresSave(true); + newClaim.getClaimStorage().save(); + GriefDefenderPlugin.getInstance().getLogger().info("Successfully migrated WorldGuard region data '" + rname + "' to '" + newClaim.getClaimStorage().filePath + "'"); + count++; + } + GriefDefenderPlugin.getInstance().getLogger().info("Finished WorldGuard region data migration for world '" + world.getName() + "'." + + " Migrated a total of " + count + " regions."); + } catch (IOException e) { + e.printStackTrace(); + } catch (ObjectMappingException e) { + e.printStackTrace(); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ContextGroupKeys.java b/sponge/src/main/java/com/griefdefender/permission/ContextGroupKeys.java new file mode 100644 index 0000000..309d288 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ContextGroupKeys.java @@ -0,0 +1,36 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +public class ContextGroupKeys { + + public static final String AMBIENT = "#ambient"; + public static final String ANIMAL = "#animal"; + public static final String AQUATIC = "#aquatic"; + public static final String FOOD = "#food"; + public static final String MISC = "#misc"; + public static final String MONSTER = "#monster"; + public static final String VEHICLE = "#vehicle"; +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ContextGroups.java b/sponge/src/main/java/com/griefdefender/permission/ContextGroups.java new file mode 100644 index 0000000..07c33c5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ContextGroups.java @@ -0,0 +1,49 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; + +public class ContextGroups { + + // Entity groups + public static final Context SOURCE_AMBIENT = new Context(ContextKeys.SOURCE, ContextGroupKeys.AMBIENT); + public static final Context SOURCE_ANIMAL = new Context(ContextKeys.SOURCE, ContextGroupKeys.ANIMAL); + public static final Context SOURCE_AQUATIC = new Context(ContextKeys.SOURCE, ContextGroupKeys.AQUATIC); + public static final Context SOURCE_MISC = new Context(ContextKeys.SOURCE, ContextGroupKeys.MISC); + public static final Context SOURCE_MONSTER = new Context(ContextKeys.SOURCE, ContextGroupKeys.MONSTER); + public static final Context SOURCE_VEHICLE = new Context(ContextKeys.SOURCE, ContextGroupKeys.VEHICLE); + public static final Context TARGET_AMBIENT = new Context(ContextKeys.TARGET, ContextGroupKeys.AMBIENT); + public static final Context TARGET_ANIMAL = new Context(ContextKeys.TARGET, ContextGroupKeys.ANIMAL); + public static final Context TARGET_AQUATIC = new Context(ContextKeys.TARGET, ContextGroupKeys.AQUATIC); + public static final Context TARGET_MISC = new Context(ContextKeys.TARGET, ContextGroupKeys.MISC); + public static final Context TARGET_MONSTER = new Context(ContextKeys.TARGET, ContextGroupKeys.MONSTER); + public static final Context TARGET_VEHICLE = new Context(ContextKeys.TARGET, ContextGroupKeys.VEHICLE); + + // Item groups + public static final Context SOURCE_FOOD = new Context(ContextKeys.SOURCE, ContextGroupKeys.FOOD); + public static final Context TARGET_FOOD = new Context(ContextKeys.TARGET, ContextGroupKeys.FOOD); +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissionGroup.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissionGroup.java new file mode 100644 index 0000000..444a559 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissionGroup.java @@ -0,0 +1,47 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.griefdefender.api.Group; + +public class GDPermissionGroup extends GDPermissionHolder implements Group { + + private String groupName; + + public GDPermissionGroup(String groupName) { + super(groupName); + this.groupName = groupName; + } + + @Override + public String getName() { + return this.groupName; + } + + @Override + public String getFriendlyName() { + return this.groupName; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissionHolder.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissionHolder.java new file mode 100644 index 0000000..63cc6eb --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissionHolder.java @@ -0,0 +1,72 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Subject; + + +public class GDPermissionHolder implements Subject { + + private String identifier; + private String friendlyName; + private Integer hashCode; + + public GDPermissionHolder(String identifier) { + this.identifier = identifier; + this.friendlyName = identifier; + } + + // used for default + public GDPermissionHolder(String objectName, String friendlyName) { + this.identifier = objectName; + this.friendlyName = friendlyName; + } + + @Override + public String getFriendlyName() { + if (this.friendlyName == null) { + return this.identifier; + } + return this.friendlyName; + } + + @Override + public String getIdentifier() { + return this.identifier; + } + + public org.spongepowered.api.service.permission.Subject getDefaultUser() { + return GriefDefenderPlugin.getInstance().permissionService.getDefaults(); + } + + @Override + public int hashCode() { + if (this.hashCode == null) { + this.hashCode = 31 * this.identifier.hashCode(); + } + return this.hashCode; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissionManager.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissionManager.java new file mode 100644 index 0000000..8fd1e5c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissionManager.java @@ -0,0 +1,1429 @@ +/* + + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.google.inject.Singleton; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.Subject; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.PermissionManager; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.ResultTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.EventResultCache; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.command.CommandHelper; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.category.BanCategory; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDFlagPermissionEvent; +import com.griefdefender.internal.registry.GDEntityType; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.registry.OptionRegistryModule; +import com.griefdefender.util.EntityUtils; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; +import org.spongepowered.api.CatalogType; +import org.spongepowered.api.Game; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.block.BlockState; +import org.spongepowered.api.block.BlockType; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.EntityType; +import org.spongepowered.api.entity.Item; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.entity.vehicle.Boat; +import org.spongepowered.api.entity.vehicle.minecart.Minecart; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.block.NotifyNeighborBlockEvent; +import org.spongepowered.api.event.cause.EventContextKey; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.cause.entity.damage.source.DamageSource; +import org.spongepowered.api.event.cause.entity.damage.source.EntityDamageSource; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.item.inventory.Inventory; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.item.inventory.ItemStackSnapshot; +import org.spongepowered.api.item.inventory.equipment.EquipmentTypes; +import org.spongepowered.api.plugin.PluginContainer; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.world.LocatableBlock; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.common.SpongeImplHooks; +import org.spongepowered.common.registry.type.entity.EntityTypeRegistryModule; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Singleton +public class GDPermissionManager implements PermissionManager { + + private static final Pattern BLOCKSTATE_PATTERN = Pattern.compile("(?:\\w+=\\w+,)*\\w+=\\w+", Pattern.MULTILINE); + + private static GDPermissionManager instance; + public boolean blacklistCheck = false; + private Event currentEvent; + private Location<World> eventLocation; + private GDPermissionHolder eventSubject; + private GDPlayerData eventPlayerData; + private String eventSourceId = "none"; + private String eventTargetId = "none"; + private Set<Context> eventContexts = new HashSet<>(); + private Component eventMessage; + private static final List<EventContextKey> IGNORED_EVENT_CONTEXTS = Arrays.asList(EventContextKeys.CREATOR, + EventContextKeys.FAKE_PLAYER, EventContextKeys.NOTIFIER, EventContextKeys.OWNER, EventContextKeys.PISTON_EXTEND, + EventContextKeys.PISTON_RETRACT, EventContextKeys.SERVICE_MANAGER, EventContextKeys.PLAYER_BREAK, EventContextKeys.SPAWN_TYPE); + private static final Pattern PATTERN_META = Pattern.compile("\\.[\\d+]*$"); + private static final List<Context> CONTEXT_LIST = Arrays.asList( + ClaimContexts.ADMIN_DEFAULT_CONTEXT, ClaimContexts.ADMIN_OVERRIDE_CONTEXT, + ClaimContexts.BASIC_DEFAULT_CONTEXT, ClaimContexts.BASIC_OVERRIDE_CONTEXT, + ClaimContexts.TOWN_DEFAULT_CONTEXT, ClaimContexts.TOWN_OVERRIDE_CONTEXT, + ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT, ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); + + private enum BanType { + BLOCK, + ENTITY, + ITEM + } + + public GDPermissionHolder getDefaultHolder() { + return GriefDefenderPlugin.DEFAULT_HOLDER; + } + + @Override + public Tristate getActiveFlagPermissionValue(Claim claim, Subject subject, Flag flag, Object source, Object target, Set<Context> contexts, TrustType type, boolean checkOverride) { + return getFinalPermission(null, null, contexts, claim, flag.getPermission(), source, target, (GDPermissionHolder) subject, null, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, GDPermissionHolder permissionHolder) { + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, false); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, Player player) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(player); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, false); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, Player player, boolean checkOverride) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(player); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, GDPermissionHolder permissionHolder, boolean checkOverride) { + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, User user) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(user); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, false); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, User user, boolean checkOverride) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(user); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, null, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, Player player, TrustType type, boolean checkOverride) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(player); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, type, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, User user, TrustType type, boolean checkOverride) { + final GDPermissionHolder permissionHolder = PermissionHolderCache.getInstance().getOrCreateUser(user); + return getFinalPermission(event, location, claim, flagPermission, source, target, permissionHolder, type, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Claim claim, String flagPermission, Object source, Object target, GDPermissionHolder subject, TrustType type, boolean checkOverride) { + final Set<Context> contexts = new HashSet<>(); + for (Map.Entry<EventContextKey<?>, Object> mapEntry : event.getContext().asMap().entrySet()) { + if (IGNORED_EVENT_CONTEXTS.contains(mapEntry.getKey())) { + continue; + } + final String[] parts = mapEntry.getKey().getId().split(":"); + String contextId = getPermissionIdentifier(mapEntry.getValue(), false); + final int index = contextId.indexOf("."); + if (index > -1) { + contextId = contextId.substring(0, index); + } + if (contextId.isEmpty()) { + continue; + } + final Context context = new Context(parts[1], contextId); + contexts.add(context); + } + return getFinalPermission(event, location, contexts, claim, flagPermission, source, target, subject, type, checkOverride); + } + + public Tristate getFinalPermission(Event event, Location<World> location, Set<Context> contexts, Claim claim, String flagPermission, Object source, Object target, GDPermissionHolder permissionHolder, TrustType type, boolean checkOverride) { + if (claim == null) { + return Tristate.TRUE; + } + + GDPlayerData playerData = null; + final GDPermissionUser user = permissionHolder instanceof GDPermissionUser ? (GDPermissionUser) permissionHolder : null; + this.eventSubject = null; + boolean isFakePlayer = false; + if (user != null) { + this.eventSubject = user; + if (user.getOnlinePlayer() != null) { + playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(((GDClaim) claim).getWorldUniqueId(), user.getUniqueId()); + if (NMSUtil.getInstance().isFakePlayer((Entity) user.getOnlinePlayer())) { + isFakePlayer = true; + } + } + } + + this.currentEvent = event; + this.eventLocation = location; + // refresh contexts + this.eventContexts = new HashSet<>(); + /*final ItemStackSnapshot usedItem = event.getContext().get(EventContextKeys.USED_ITEM).orElse(null); + final DamageType damageType = event.getContext().get(EventContextKeys.DAMAGE_TYPE).orElse(null); + if (usedItem != null) { + //final String id = getPermissionIdentifier(usedItem); + this.eventContexts.add(new Context("used_item", usedItem.getType().getId())); + } + if (damageType != null) { + //final String id = getPermissionIdentifier(damageType); + this.eventContexts.add(new Context("damage_type", damageType.getId())); + }*/ + if (isFakePlayer && source instanceof Game) { + source = user.getOnlinePlayer(); + } + if (user != null) { + if (user.getOnlinePlayer() != null) { + this.addPlayerContexts(user.getOnlinePlayer(), contexts); + } + } + + final Set<Context> sourceContexts = this.getPermissionContexts((GDClaim) claim, source, true); + if (sourceContexts == null) { + return Tristate.FALSE; + } + + final Set<Context> targetContexts = this.getPermissionContexts((GDClaim) claim, target, false); + if (targetContexts == null) { + return Tristate.FALSE; + } + contexts.addAll(sourceContexts); + contexts.addAll(targetContexts); + this.eventContexts = contexts; + this.eventPlayerData = playerData; + + String targetPermission = flagPermission; + /*if (!targetId.isEmpty()) { + //String[] parts = targetId.split(":"); + //String targetMod = parts[0]; + // move target meta to end of permission + Matcher m = PATTERN_META.matcher(targetId); + String targetMeta = ""; + if (!flagPermission.contains("command-execute")) { + if (m.find()) { + targetMeta = m.group(0); + targetId = StringUtils.replace(targetId, targetMeta, ""); + } + } + targetPermission += "." + targetId + targetMeta; + }*/ + + targetPermission = StringUtils.replace(targetPermission, ":", "."); + // If player can ignore admin claims and is currently ignoring , allow + if (user != null && playerData != null && !playerData.debugClaimPermissions && playerData.canIgnoreClaim(claim)) { + return processResult(claim, targetPermission, "ignore", Tristate.TRUE, user); + } + if (checkOverride) { + Tristate override = Tristate.UNDEFINED; + // First check for claim flag overrides + override = getFlagOverride(claim, permissionHolder == null ? GriefDefenderPlugin.DEFAULT_HOLDER : permissionHolder, playerData, targetPermission); + if (override != Tristate.UNDEFINED) { + return override; + } + } + + if (playerData != null && user != null) { + if (playerData.debugClaimPermissions) { + if (type != null && claim.isUserTrusted(user.getUniqueId(), type)) { + return processResult(claim, targetPermission, type.getName().toLowerCase(), Tristate.TRUE, user); + } + return getClaimFlagPermission(claim, targetPermission); + } + // Check for ignoreclaims after override and debug checks + if (playerData.canIgnoreClaim(claim)) { + return processResult(claim, targetPermission, "ignore", Tristate.TRUE, user); + } + } + if (user != null) { + if (type != null) { + if (((GDClaim) claim).isUserTrusted(user, type)) { + return processResult(claim, targetPermission, type.getName().toLowerCase(), Tristate.TRUE, permissionHolder); + } + } + return getUserPermission(user, claim, targetPermission); + } + + return getClaimFlagPermission(claim, targetPermission); + } + + private Tristate getUserPermission(GDPermissionHolder holder, Claim claim, String permission) { + final List<Claim> inheritParents = claim.getInheritedParents(); + final Set<Context> contexts = new HashSet<>(); + contexts.addAll(this.eventContexts); + + for (Claim parentClaim : inheritParents) { + GDClaim parent = (GDClaim) parentClaim; + // check parent context + contexts.add(parent.getContext()); + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, holder, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, holder); + } + + contexts.remove(parent.getContext()); + } + + contexts.add(claim.getContext()); + contexts.add(claim.getType().getContext()); + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, holder, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, holder); + } + + if (holder == GriefDefenderPlugin.DEFAULT_HOLDER) { + return getFlagDefaultPermission(claim, permission, contexts); + } + + return getClaimFlagPermission(claim, permission, contexts); + } + + private Tristate getClaimFlagPermission(Claim claim, String permission) { + return this.getClaimFlagPermission(claim, permission, new HashSet<>()); + } + + private Tristate getClaimFlagPermission(Claim claim, String permission, Set<Context> contexts) { + if (contexts.isEmpty()) { + final List<Claim> inheritParents = claim.getInheritedParents(); + contexts.addAll(this.eventContexts); + for (Claim parentClaim : inheritParents) { + GDClaim parent = (GDClaim) parentClaim; + // check parent context + contexts.add(parent.getContext()); + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, GriefDefenderPlugin.DEFAULT_HOLDER, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, GriefDefenderPlugin.DEFAULT_HOLDER); + } + + contexts.remove(parent.getContext()); + } + contexts.add(claim.getContext()); + } + + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, GriefDefenderPlugin.DEFAULT_HOLDER, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, GriefDefenderPlugin.DEFAULT_HOLDER); + } + + return getFlagDefaultPermission(claim, permission, contexts); + } + + // Only uses world and claim type contexts + private Tristate getFlagDefaultPermission(Claim claim, String permission, Set<Context> contexts) { + contexts.add(claim.getDefaultTypeContext()); + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, GriefDefenderPlugin.DEFAULT_HOLDER, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, GriefDefenderPlugin.DEFAULT_HOLDER); + } + contexts.remove(claim.getDefaultTypeContext()); + contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, GriefDefenderPlugin.DEFAULT_HOLDER, permission, contexts); + if (value != Tristate.UNDEFINED) { + return processResult(claim, permission, value, GriefDefenderPlugin.DEFAULT_HOLDER); + } + + return processResult(claim, permission, Tristate.UNDEFINED, GriefDefenderPlugin.DEFAULT_HOLDER); + } + + private Tristate getFlagOverride(Claim claim, GDPermissionHolder permissionHolder, GDPlayerData playerData, String flagPermission) { + if (!((GDClaim) claim).getInternalClaimData().allowFlagOverrides()) { + return Tristate.UNDEFINED; + } + + Player player = null; + Set<Context> contexts = new HashSet<>(); + if (claim.isAdminClaim()) { + contexts.add(ClaimContexts.ADMIN_OVERRIDE_CONTEXT); + //contexts.add(claim.world.getContext()); + } else if (claim.isTown()) { + contexts.add(ClaimContexts.TOWN_OVERRIDE_CONTEXT); + //contexts.add(claim.world.getContext()); + } else if (claim.isBasicClaim()) { + contexts.add(ClaimContexts.BASIC_OVERRIDE_CONTEXT); + //contexts.add(claim.world.getContext()); + } else if (claim.isWilderness()) { + contexts.add(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT); + player = permissionHolder instanceof GDPermissionUser ? ((GDPermissionUser) permissionHolder).getOnlinePlayer() : null; + } + + contexts.add(claim.getOverrideClaimContext()); + contexts.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + contexts.addAll(this.eventContexts); + + Tristate value = PermissionUtil.getInstance().getPermissionValue((GDClaim) claim, permissionHolder, flagPermission, contexts); + /* if (value == Tristate.UNDEFINED) { + // Check claim specific override + contexts = PermissionUtils.getActiveContexts(subject, playerData, claim); + contexts.add(claim.getContext()); + contexts.add(ClaimContexts.CLAIM_OVERRIDE_CONTEXT); + value = subject.getPermissionValue(contexts, flagPermission); + }*/ + if (value != Tristate.UNDEFINED) { + if (value == Tristate.FALSE) { + this.eventMessage = MessageCache.getInstance().PERMISSION_OVERRIDE_DENY; + } + return processResult(claim, flagPermission, value, permissionHolder); + } + if (permissionHolder != GriefDefenderPlugin.DEFAULT_HOLDER) { + return getFlagOverride(claim, GriefDefenderPlugin.DEFAULT_HOLDER, playerData, flagPermission); + } + + return Tristate.UNDEFINED; + } + + public Tristate processResult(Claim claim, String permission, Tristate permissionValue, GDPermissionHolder permissionHolder) { + return processResult(claim, permission, null, permissionValue, permissionHolder); + } + + public Tristate processResult(Claim claim, String permission, String trust, Tristate permissionValue, GDPermissionHolder permissionHolder) { + if (GriefDefenderPlugin.debugActive) { + // Use the event subject always if available + // This prevents debug showing 'default' for users + if (eventSubject != null) { + permissionHolder = eventSubject; + } else if (permissionHolder == null) { + final Object source = GDCauseStackManager.getInstance().getCurrentCause().root(); + if (source instanceof GDPermissionUser) { + permissionHolder = (GDPermissionUser) source; + } else { + permissionHolder = GriefDefenderPlugin.DEFAULT_HOLDER; + } + } + + if (this.currentEvent != null && (this.currentEvent instanceof NotifyNeighborBlockEvent)) { + if (((GDClaim) claim).getWorld().getProperties().getTotalTime() % 100 != 0L) { + return permissionValue; + } + } + + GriefDefenderPlugin.addEventLogEntry(this.currentEvent, this.eventLocation, this.eventSourceId, this.eventTargetId, permissionHolder, permission, trust, permissionValue); + } + + if (eventPlayerData != null && eventPlayerData.eventResultCache != null) { + final Flag flag = FlagRegistryModule.getInstance().getById(permission).orElse(null); + if (flag != null) { + eventPlayerData.eventResultCache = new EventResultCache((GDClaim) claim, flag.getName().toLowerCase(), permissionValue, trust); + } + } + return permissionValue; + } + + public void addEventLogEntry(Event event, Location<World> location, Object source, Object target, User user, String permission, String trust, Tristate result) { + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateUser(user); + addEventLogEntry(event, location, source, target, holder, permission, trust, result); + } + + // Used for situations where events are skipped for perf reasons + public void addEventLogEntry(Event event, Location<World> location, Object source, Object target, GDPermissionHolder permissionSubject, String permission, String trust, Tristate result) { + if (GriefDefenderPlugin.debugActive) { + String sourceId = getPermissionIdentifier(source, true); + String targetPermission = permission; + String targetId = getPermissionIdentifier(target); + /*if (!targetId.isEmpty()) { + // move target meta to end of permission + Matcher m = PATTERN_META.matcher(targetId); + String targetMeta = ""; + if (!permission.contains("command-execute")) { + if (m.find()) { + targetMeta = m.group(0); + targetId = StringUtils.replace(targetId, targetMeta, ""); + } + } + targetPermission += "." + targetId + targetMeta; + }*/ + if (permissionSubject == null) { + permissionSubject = GriefDefenderPlugin.DEFAULT_HOLDER; + } + GriefDefenderPlugin.addEventLogEntry(event, location, sourceId, targetId, permissionSubject, targetPermission, trust, result); + } + } + + public String getPermissionIdentifier(Object obj) { + return getPermissionIdentifier(obj, false); + } + + public String getPermissionIdentifier(Object obj, boolean isSource) { + if (obj != null) { + if (obj instanceof Entity) { + return populateEventSourceTarget(NMSUtil.getInstance().getEntityId((Entity) obj, isSource), isSource); + } else if (obj instanceof EntityType) { + final String id = ((EntityType) obj).getId(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof BlockType) { + final String id = ((BlockType) obj).getId(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof BlockSnapshot) { + final BlockSnapshot blockSnapshot = (BlockSnapshot) obj; + final BlockState blockstate = blockSnapshot.getState(); + String id = blockstate.getType().getId() + "." + BlockUtil.getInstance().getBlockStateMeta(blockstate); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof BlockState) { + final BlockState blockstate = (BlockState) obj; + final String id = blockstate.getType().getId() + "." + BlockUtil.getInstance().getBlockStateMeta(blockstate); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof LocatableBlock) { + final LocatableBlock locatableBlock = (LocatableBlock) obj; + final BlockState blockstate = locatableBlock.getBlockState(); + final String id = blockstate.getType().getId() + "." + BlockUtil.getInstance().getBlockStateMeta(blockstate); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof TileEntity) { + TileEntity tileEntity = (TileEntity) obj; + final String id = tileEntity.getType().getId().toLowerCase(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof ItemStack) { + return populateEventSourceTarget(NMSUtil.getInstance().getItemStackId((ItemStack) obj), isSource); + } else if (obj instanceof ItemType) { + final String id = ((ItemType) obj).getId().toLowerCase(); + populateEventSourceTarget(id, isSource); + return id; + } else if (obj instanceof EntityDamageSource) { + final EntityDamageSource damageSource = (EntityDamageSource) obj; + Entity sourceEntity = damageSource.getSource(); + + if (this.eventSubject == null && sourceEntity instanceof User) { + this.eventSubject = PermissionHolderCache.getInstance().getOrCreateUser((User) sourceEntity); + } + + return getPermissionIdentifier(sourceEntity, isSource); + } else if (obj instanceof DamageSource) { + final DamageSource damageSource = (DamageSource) obj; + String id = damageSource.getType().getId(); + if (!id.contains(":")) { + id = "minecraft:" + id; + } + + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof ItemStackSnapshot) { + final ItemStackSnapshot itemSnapshot = ((ItemStackSnapshot) obj); + return getPermissionIdentifier(itemSnapshot.createStack(), isSource); + } else if (obj instanceof CatalogType) { + final String id = ((CatalogType) obj).getId(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof String) { + final String id = obj.toString().toLowerCase(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof PluginContainer) { + final String id = ((PluginContainer) obj).getId(); + return populateEventSourceTarget(id, isSource); + } else if (obj instanceof Inventory) { + return ((Inventory) obj).getArchetype().getId(); + } else if (obj instanceof Location) { + return getPermissionIdentifier(((Location) obj).getBlock(), isSource); + } + } + + populateEventSourceTarget("none", isSource); + return ""; + } + + public Set<Context> getPermissionContexts(GDClaim claim, Object obj, boolean isSource) { + final Set<Context> contexts = new HashSet<>(); + if (obj != null) { + if (obj instanceof Entity) { + + Entity targetEntity = (Entity) obj; + net.minecraft.entity.Entity mcEntity = null; + if (targetEntity instanceof net.minecraft.entity.Entity) { + mcEntity = (net.minecraft.entity.Entity) targetEntity; + } + String id = ""; + if (targetEntity.getType() != null) { + id = targetEntity.getType().getId(); + } + + if (mcEntity != null && id.contains("unknown") && SpongeImplHooks.isFakePlayer(mcEntity)) { + final String modId = SpongeImplHooks.getModIdFromClass(mcEntity.getClass()); + id = modId + ":fakeplayer_" + EntityUtils.getFriendlyName(mcEntity).toLowerCase(); + } else if (id.equals("unknown:unknown") && obj instanceof EntityPlayer) { + id = "minecraft:player"; + } + + if (mcEntity != null && targetEntity instanceof Living) { + String[] parts = id.split(":"); + if (parts.length > 1) { + final String modId = parts[0]; + String name = parts[1]; + if (modId.equalsIgnoreCase("pixelmon") && modId.equalsIgnoreCase(name)) { + name = EntityUtils.getFriendlyName(mcEntity).toLowerCase(); + id = modId + ":" + name; + } + } + } + + if (this.isObjectIdBanned(claim, id, BanType.ENTITY)) { + return null; + } + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof EntityType) { + final String id = ((EntityType) obj).getId(); + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof BlockType) { + final String id = ((BlockType) obj).getId(); + if (this.isObjectIdBanned(claim, id, BanType.BLOCK)) { + return null; + } + populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof BlockSnapshot) { + final BlockSnapshot blockSnapshot = (BlockSnapshot) obj; + final BlockState blockstate = blockSnapshot.getState(); + return this.getPermissionContexts(claim, blockstate, isSource); + } else if (obj instanceof BlockState) { + final BlockState blockstate = (BlockState) obj; + if (this.isObjectIdBanned(claim, blockstate.getType().getId(), BanType.BLOCK)) { + return null; + } + final String id = blockstate.getType().getId(); + contexts.add(new Context("meta", String.valueOf(BlockUtil.getInstance().getBlockStateMeta(blockstate)))); + this.addBlockPropertyContexts(contexts, blockstate); + if (this.isObjectIdBanned(claim, id, BanType.BLOCK)) { + return null; + } + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof LocatableBlock) { + final LocatableBlock locatableBlock = (LocatableBlock) obj; + final BlockState blockstate = locatableBlock.getBlockState(); + return this.getPermissionContexts(claim, blockstate, isSource); + } else if (obj instanceof TileEntity) { + TileEntity tileEntity = (TileEntity) obj; + final String id = tileEntity.getType().getId().toLowerCase(); + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof ItemStack) { + final ItemStack itemstack = (ItemStack) obj; + if (NMSUtil.getInstance().isItemFood(itemstack.getType())) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_FOOD); + } else { + contexts.add(ContextGroups.TARGET_FOOD); + } + } + if (this.isObjectIdBanned(claim, itemstack.getType().getId(), BanType.ITEM)) { + return null; + } + final String id = NMSUtil.getInstance().getItemStackId(itemstack); + contexts.add(new Context("meta", NMSUtil.getInstance().getItemStackMeta(itemstack))); + if (this.isObjectIdBanned(claim, id, BanType.ITEM)) { + return null; + } + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof ItemType) { + final String id = ((ItemType) obj).getId().toLowerCase(); + if (NMSUtil.getInstance().isItemFood(((ItemType) obj))) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_FOOD); + } else { + contexts.add(ContextGroups.TARGET_FOOD); + } + } + if (this.isObjectIdBanned(claim, id, BanType.ITEM)) { + return null; + } + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof EntityDamageSource) { + final EntityDamageSource damageSource = (EntityDamageSource) obj; + Entity sourceEntity = damageSource.getSource(); + + if (this.eventSubject == null && sourceEntity instanceof User) { + this.eventSubject = PermissionHolderCache.getInstance().getOrCreateUser((User) sourceEntity); + } + + return this.getPermissionContexts(claim, sourceEntity, isSource); + } else if (obj instanceof DamageSource) { + final DamageSource damageSource = (DamageSource) obj; + String id = damageSource.getType().getId(); + if (!id.contains(":")) { + id = "minecraft:" + id; + } + + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof ItemStackSnapshot) { + final ItemStackSnapshot itemSnapshot = ((ItemStackSnapshot) obj); + return this.getPermissionContexts(claim, itemSnapshot.createStack(), isSource); + } else if (obj instanceof CatalogType) { + final String id = ((CatalogType) obj).getId(); + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof String) { + final String id = obj.toString().toLowerCase(); + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof PluginContainer) { + final String id = ((PluginContainer) obj).getId(); + return populateEventSourceTargetContext(contexts, id, isSource); + } else if (obj instanceof Inventory) { + return populateEventSourceTargetContext(contexts, ((Inventory) obj).getArchetype().getId(), isSource); + } else if (obj instanceof Location) { + return this.getPermissionContexts(claim, ((Location) obj).getBlock(), isSource); + } + } + + return contexts; + } + + public boolean isObjectIdBanned(GDClaim claim, String id, BanType type) { + if (id.equalsIgnoreCase("player")) { + return false; + } + + GDPermissionUser user = null; + if (this.eventSubject != null && this.eventSubject instanceof GDPermissionUser) { + user = (GDPermissionUser) this.eventSubject; + if (user.getInternalPlayerData() != null && user.getInternalPlayerData().canIgnoreClaim(claim)) { + return false; + } + } + + final String permission = StringUtils.replace(id, ":", "."); + Component banReason = null; + final BanCategory banCategory = GriefDefenderPlugin.getGlobalConfig().getConfig().bans; + if (type == BanType.BLOCK) { + for (Entry<String, Component> banId : banCategory.getBlockMap().entrySet()) { + if (FilenameUtils.wildcardMatch(id, banId.getKey())) { + banReason = GriefDefenderPlugin.getGlobalConfig().getConfig().bans.getBlockBanReason(banId.getKey()); + if (banReason != null && banReason.equals(TextComponent.empty())) { + banReason = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_BAN_BLOCK, + ImmutableMap.of("id", TextComponent.of(id, TextColor.GOLD))); + } + break; + } + } + } else if (type == BanType.ITEM) { + for (Entry<String, Component> banId : banCategory.getItemMap().entrySet()) { + if (FilenameUtils.wildcardMatch(id, banId.getKey())) { + banReason = GriefDefenderPlugin.getGlobalConfig().getConfig().bans.getItemBanReason(banId.getKey()); + if (banReason != null && banReason.equals(TextComponent.empty())) { + banReason = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_BAN_ITEM, + ImmutableMap.of("id", TextComponent.of(id, TextColor.GOLD))); + } + } + } + } else if (type == BanType.ENTITY) { + for (Entry<String, Component> banId : banCategory.getEntityMap().entrySet()) { + if (FilenameUtils.wildcardMatch(id, banId.getKey())) { + banReason = GriefDefenderPlugin.getGlobalConfig().getConfig().bans.getEntityBanReason(banId.getKey()); + if (banReason != null && banReason.equals(TextComponent.empty())) { + banReason = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_BAN_ENTITY, + ImmutableMap.of("id", TextComponent.of(id, TextColor.GOLD))); + } + } + } + } + + if (banReason != null && user != null) { + final Player player = user.getOnlinePlayer(); + if (player != null) { + if (banReason.equals(TextComponent.empty())) { + banReason = MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.PERMISSION_BAN_BLOCK, + ImmutableMap.of("id", id)); + } + TextAdapter.sendComponent(player, banReason); + this.processResult(claim, permission, "banned", Tristate.FALSE, user); + return true; + } + } + if (banReason != null) { + // Detected ban + this.processResult(claim, permission, "banned", Tristate.FALSE, this.eventSubject); + return true; + } + return false; + } + + public void addCustomEntityTypeContexts(Entity targetEntity, Set<Context> contexts, GDEntityType type, boolean isSource) { + // check vehicle + if (targetEntity instanceof Boat || targetEntity instanceof Minecart) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_VEHICLE); + } else { + contexts.add(ContextGroups.TARGET_VEHICLE); + } + } + final String creatureType = type.getEnumCreatureTypeId(); + if (creatureType == null) { + return; + } + + final String contextKey = isSource ? "source" : "target"; + //contexts.add(new Context(contextKey, "#" + creatureType)); + if (creatureType.contains("animal")) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_ANIMAL); + } else { + contexts.add(ContextGroups.TARGET_ANIMAL); + } + } else if (creatureType.contains("aquatic")) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_AQUATIC); + } else { + contexts.add(ContextGroups.TARGET_AQUATIC); + } + } else if (creatureType.contains("monster")) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_MONSTER); + } else { + contexts.add(ContextGroups.TARGET_MONSTER); + } + } else if (creatureType.contains("ambient")) { + if (isSource) { + contexts.add(ContextGroups.SOURCE_AMBIENT); + } else { + contexts.add(ContextGroups.TARGET_AMBIENT); + } + } else { + if (isSource) { + contexts.add(ContextGroups.SOURCE_MISC); + } else { + contexts.add(ContextGroups.TARGET_MISC); + } + } + } + + private void addPlayerContexts(Player player, Set<Context> contexts) { + if(!PermissionUtil.getInstance().containsKey(contexts, "used_item") && NMSUtil.getInstance().getActiveItem(player, this.currentEvent) != null) { + final ItemStack stack = NMSUtil.getInstance().getActiveItem(player, this.currentEvent); + if (!stack.isEmpty()) { + contexts.add(new Context("used_item", getPermissionIdentifier(stack))); + final Text displayName = stack.get(Keys.DISPLAY_NAME).orElse(null); + if (displayName != null) { + String itemName = displayName.toPlain().replaceAll("[^A-Za-z0-9]", "").toLowerCase(); + if (itemName != null && !itemName.isEmpty()) { + if (!itemName.contains(":")) { + itemName = "minecraft:" + itemName; + } + contexts.add(new Context("item_name", itemName)); + } + } + } + } + final ItemStack helmet = player.getEquipped(EquipmentTypes.HEADWEAR).orElse(null); + final ItemStack chestplate = player.getEquipped(EquipmentTypes.CHESTPLATE).orElse(null); + final ItemStack leggings = player.getEquipped(EquipmentTypes.LEGGINGS).orElse(null); + final ItemStack boots = player.getEquipped(EquipmentTypes.BOOTS).orElse(null); + if (helmet != null && !helmet.isEmpty()) { + contexts.add(new Context("helmet", getPermissionIdentifier(helmet))); + } + if (chestplate != null && !chestplate.isEmpty()) { + contexts.add(new Context("chestplate", getPermissionIdentifier(chestplate))); + } + if (leggings != null && !leggings.isEmpty()) { + contexts.add(new Context("leggings", getPermissionIdentifier(leggings))); + } + if (boots != null && !boots.isEmpty()) { + contexts.add(new Context("boots", getPermissionIdentifier(boots))); + } + } + + private Set<Context> addBlockPropertyContexts(Set<Context> contexts, BlockState block) { + Matcher matcher = BLOCKSTATE_PATTERN.matcher(block.toString()); + if (matcher.find()) { + final String properties[] = matcher.group(0).split(","); + for (String property : properties) { + contexts.add(new Context("state", property.replace("=", ":"))); + } + } + return contexts; + } + + public String getSourcePermission(String flagPermission) { + final int index = flagPermission.indexOf(".source."); + if (index != -1) { + return flagPermission.substring(index + 8); + } + + return null; + } + + public String getTargetPermission(String flagPermission) { + flagPermission = StringUtils.replace(flagPermission, "griefdefender.flag.", ""); + boolean found = false; + for (Flag flag : FlagRegistryModule.getInstance().getAll()) { + if (flagPermission.contains(flag.toString() + ".")) { + found = true; + } + flagPermission = StringUtils.replace(flagPermission, flag.toString() + ".", ""); + } + if (!found) { + return null; + } + final int sourceIndex = flagPermission.indexOf(".source."); + if (sourceIndex != -1) { + flagPermission = StringUtils.replace(flagPermission, flagPermission.substring(sourceIndex, flagPermission.length()), ""); + } + + return flagPermission; + } + + // Used for debugging + public String getPermission(Object source, Object target, String flagPermission) { + String sourceId = getPermissionIdentifier(source, true); + String targetPermission = flagPermission; + String targetId = getPermissionIdentifier(target); + if (!targetId.isEmpty()) { + if (!sourceId.isEmpty()) { + // move target meta to end of permission + Matcher m = PATTERN_META.matcher(targetId); + String targetMeta = ""; + if (m.find()) { + targetMeta = m.group(0); + targetId = StringUtils.replace(targetId, targetMeta, ""); + } + targetPermission += "." + targetId + ".source." + sourceId + targetMeta; + } else { + targetPermission += "." + targetId; + } + } + targetPermission = StringUtils.replace(targetPermission, ":", "."); + return targetPermission; + } + + public String getIdentifierWithoutMeta(String targetId) { + Matcher m = PATTERN_META.matcher(targetId); + String targetMeta = ""; + if (m.find()) { + targetMeta = m.group(0); + targetId = StringUtils.replace(targetId, targetMeta, ""); + } + return targetId; + } + + private Set<Context> populateEventSourceTargetContext(Set<Context> contexts, String id, boolean isSource) { + if (!id.contains(":")) { + id = "minecraft:" + id; + } + if (isSource) { + this.eventSourceId = id.toLowerCase(); + contexts.add(new Context("source", this.eventSourceId)); + } else { + this.eventTargetId = id.toLowerCase(); + contexts.add(new Context("target", this.eventTargetId)); + } + return contexts; + } + + public String populateEventSourceTarget(String id, boolean isSource) { + if (this.blacklistCheck) { + return id; + } + + if (!id.contains(":")) { + id = "minecraft:" + id; + } + String[] parts = id.split(":"); + if (parts != null && parts.length == 3) { + if (parts[0].equals(parts[1])) { + id = parts[1] + ":" + parts[2]; + } + } + if (isSource) { + this.eventSourceId = id.toLowerCase(); + } else { + this.eventTargetId = id.toLowerCase(); + } + + return id; + } + + @Override + public CompletableFuture<PermissionResult> clearAllFlagPermissions(Subject subject) { + CompletableFuture<PermissionResult> result = new CompletableFuture<>(); + if (subject == null) { + result.complete(new GDPermissionResult(ResultTypes.SUBJECT_DOES_NOT_EXIST)); + return result; + } + + GDFlagPermissionEvent.ClearAll event = new GDFlagPermissionEvent.ClearAll(subject); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + result.complete(new GDPermissionResult(ResultTypes.EVENT_CANCELLED, event.getMessage().orElse(null))); + return result; + } + + for (Map.Entry<Set<Context>, Map<String, Boolean>> mapEntry : PermissionUtil.getInstance().getPermanentPermissions((GDPermissionHolder) subject).entrySet()) { + final Set<Context> contextSet = mapEntry.getKey(); + for (Context context : contextSet) { + if (context.getValue().equals(subject.getIdentifier())) { + PermissionUtil.getInstance().clearPermissions((GDPermissionHolder) subject, context); + } + } + } + + result.complete(new GDPermissionResult(ResultTypes.SUCCESS)); + return result; + } + + @Override + public CompletableFuture<PermissionResult> clearFlagPermissions(Set<Context> contexts) { + return clearFlagPermissions(GriefDefenderPlugin.DEFAULT_HOLDER, contexts); + } + + @Override + public CompletableFuture<PermissionResult> clearFlagPermissions(Subject subject, Set<Context> contexts) { + CompletableFuture<PermissionResult> result = new CompletableFuture<>(); + if (subject == null) { + result.complete(new GDPermissionResult(ResultTypes.SUBJECT_DOES_NOT_EXIST)); + } + + GDFlagPermissionEvent.Clear event = new GDFlagPermissionEvent.Clear(subject, contexts); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + result.complete(new GDPermissionResult(ResultTypes.EVENT_CANCELLED, event.getMessage().orElse(null))); + return result; + } + + //contexts.add(((GDClaim) claim).getWorld().getContext()); + PermissionUtil.getInstance().clearPermissions((GDPermissionHolder) subject, contexts); + result.complete(new GDPermissionResult(ResultTypes.SUCCESS)); + return result; + } + + @Override + public CompletableFuture<PermissionResult> setFlagPermission(Flag flag, Tristate value, Set<Context> contexts) { + return setPermission(GriefDefenderPlugin.DEFAULT_HOLDER, flag, value, contexts); + } + + public CompletableFuture<PermissionResult> setPermission(Subject subject, Flag flag, Tristate value, Set<Context> contexts) { + CompletableFuture<PermissionResult> result = new CompletableFuture<>(); + + GDFlagPermissionEvent.Set event = new GDFlagPermissionEvent.Set(subject, flag, value, contexts); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + result.complete(new GDPermissionResult(ResultTypes.EVENT_CANCELLED, event.getMessage().orElse(null))); + return result; + } + + result.complete(PermissionUtil.getInstance().setPermissionValue((GDPermissionHolder) subject, flag, value, contexts)); + return result; + } + + // internal + public CompletableFuture<PermissionResult> setPermission(Claim claim, GDPermissionHolder subject, Flag flag, String target, Tristate value, Set<Context> contexts) { + if (target.equalsIgnoreCase("any:any")) { + target = "any"; + } + + CompletableFuture<PermissionResult> result = new CompletableFuture<>(); + if (flag != Flags.COMMAND_EXECUTE && flag != Flags.COMMAND_EXECUTE_PVP && !target.contains("pixelmon")) { + String[] parts = target.split(":"); + if (!target.startsWith("#") && parts.length > 1 && parts[0].equalsIgnoreCase("minecraft")) { + target = parts[1]; + } + + if (target != null && !GriefDefenderPlugin.ID_MAP.contains(target)) { + result.complete(new GDPermissionResult(ResultTypes.TARGET_NOT_VALID)); + return result; + } + } + + contexts.add(new Context(ContextKeys.TARGET, target)); + GDFlagPermissionEvent.Set event = new GDFlagPermissionEvent.Set(subject, flag, value, contexts); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + result.complete(new GDPermissionResult(ResultTypes.EVENT_CANCELLED, event.getMessage().orElse(null))); + return result; + } + + CommandSource commandSource = Sponge.getServer().getConsole(); + final Object root = GDCauseStackManager.getInstance().getCurrentCause().root(); + if (root instanceof CommandSource) { + commandSource = (CommandSource) root; + } + result.complete(CommandHelper.addFlagPermission(commandSource, subject, claim, flag, target, value, contexts)); + return result; + } + + @Override + public Tristate getFlagPermissionValue(Flag flag, Set<Context> contexts) { + return getPermissionValue(GriefDefenderPlugin.DEFAULT_HOLDER, flag, contexts); + } + + public Tristate getPermissionValue(GDPermissionHolder subject, Flag flag, Set<Context> contexts) { + return PermissionUtil.getInstance().getPermissionValue(subject, flag.getPermission(), contexts); + } + + @Override + public Map<String, Boolean> getFlagPermissions(Set<Context> contexts) { + return getFlagPermissions(GriefDefenderPlugin.DEFAULT_HOLDER, contexts); + } + + @Override + public Map<String, Boolean> getFlagPermissions(Subject subject, Set<Context> contexts) { + if (subject == null) { + return new HashMap<>(); + } + return PermissionUtil.getInstance().getPermissions((GDPermissionHolder) subject, contexts); + } + + public static GDPermissionManager getInstance() { + return instance; + } + + static { + instance = new GDPermissionManager(); + } + + @Override + public Optional<String> getOptionValue(Option option, Set<Context> contexts) { + return Optional.empty(); + } + + @Override + public Optional<String> getOptionValue(Subject subject, Option option, Set<Context> contexts) { + return Optional.empty(); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, User player, Option<T> option) { + return getInternalOptionValue(type, player, option, null); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, User player, Option<T> option, Claim claim) { + final GDPermissionHolder holder = PermissionHolderCache.getInstance().getOrCreateHolder(player.getUniqueId().toString()); + if (claim != null) { + return this.getInternalOptionValue(type, holder, option, claim, claim.getType(), new HashSet<>()); + } + return this.getInternalOptionValue(type, holder, option, (ClaimType) null); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, GDPermissionHolder holder, Option<T> option) { + return this.getInternalOptionValue(type, holder, option, (ClaimType) null); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, GDPermissionHolder holder, Option<T> option, Claim claim) { + if (claim != null) { + return this.getInternalOptionValue(type, holder, option, claim, claim.getType(), new HashSet<>()); + } + return this.getInternalOptionValue(type, holder, option, (ClaimType) null); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, GDPermissionHolder holder, Option<T> option, Claim claim, Set<Context> contexts) { + return getInternalOptionValue(type, holder, option, claim, null, contexts); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, GDPermissionHolder holder, Option<T> option, ClaimType claimType) { + return this.getInternalOptionValue(type, holder, option, null, claimType, new HashSet<>()); + } + + public <T> T getInternalOptionValue(TypeToken<T> type, GDPermissionHolder holder, Option<T> option, Claim claim, ClaimType claimType, Set<Context> contexts) { + if (holder != GriefDefenderPlugin.DEFAULT_HOLDER && holder instanceof GDPermissionUser) { + final GDPermissionUser user = (GDPermissionUser) holder; + final GDPlayerData playerData = (GDPlayerData) user.getPlayerData(); + if (playerData != null) { + playerData.ignoreActiveContexts = true; + } + //contexts.addAll(PermissionUtil.getInstance().getActiveContexts(holder)); + PermissionUtil.getInstance().addActiveContexts(contexts, holder, playerData, claim); + } + + if (!option.isGlobal() && (claim != null || claimType != null)) { + // check claim + if (claim != null) { + contexts.add(claim.getContext()); + String value = PermissionUtil.getInstance().getOptionValue(holder, option, contexts); + if (value != null) { + return this.getOptionTypeValue(type, value); + } + contexts.remove(claim.getContext()); + } + + // check claim type + if (claimType != null) { + contexts.add(claimType.getContext()); + String value = PermissionUtil.getInstance().getOptionValue(holder, option, contexts); + if (value != null) { + return this.getOptionTypeValue(type, value); + } + contexts.remove(claimType.getContext()); + } + } + + String value = PermissionUtil.getInstance().getOptionValue(holder, option, contexts); + // Check only active contexts + if (value != null) { + return this.getOptionTypeValue(type, value); + } + + // Check type/global default context + if (claimType != null) { + contexts.add(claimType.getDefaultContext()); + } + contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + value = PermissionUtil.getInstance().getOptionValue(holder, option, contexts); + if (value != null) { + return this.getOptionTypeValue(type, value); + } + contexts.remove(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + if (claimType != null) { + contexts.remove(claimType.getDefaultContext()); + } + + // Check global + if (holder != GriefDefenderPlugin.DEFAULT_HOLDER) { + return getInternalOptionValue(type, GriefDefenderPlugin.DEFAULT_HOLDER, option, claim, claimType, contexts); + } + + return option.getDefaultValue(); + } + + private <T> T getOptionTypeValue(TypeToken<T> type, String value) { + if (type.getRawType().isAssignableFrom(Double.class)) { + return (T) Double.valueOf(value); + } + if (type.getRawType().isAssignableFrom(Integer.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) Integer.valueOf(-1); + } + Integer val = null; + try { + val = Integer.valueOf(value); + } catch (NumberFormatException e) { + return (T) Integer.valueOf(-1); + } + return (T) Integer.valueOf(value); + } + if (type.getRawType().isAssignableFrom(String.class)) { + return (T) value; + } + if (type.getRawType().isAssignableFrom(Tristate.class)) { + if (value.equalsIgnoreCase("true")) { + return (T) Tristate.TRUE; + } + if (value.equalsIgnoreCase("false")) { + return (T) Tristate.FALSE; + } + int permValue = 0; + try { + permValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + + } + if (permValue == 0) { + return (T) Tristate.UNDEFINED; + } + return (T) (permValue == 1 ? Tristate.TRUE : Tristate.FALSE); + } + if (type.getRawType().isAssignableFrom(CreateModeType.class)) { + if (value.equalsIgnoreCase("undefined")) { + return (T) CreateModeTypes.AREA; + } + int permValue = 0; + try { + permValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + + } + if (permValue == 0) { + return (T) CreateModeTypes.AREA; + } + return (T) (permValue == 1 ? CreateModeTypes.VOLUME : CreateModeTypes.AREA); + } + if (type.getRawType().isAssignableFrom(Boolean.class)) { + return (T) Boolean.valueOf(Boolean.parseBoolean(value)); + } + return (T) value; + } + + // Uses passed contexts and only adds active contexts + public Double getActualOptionValue(GDPermissionHolder holder, Option option, Claim claim, GDPlayerData playerData, Set<Context> contexts) { + if (holder != GriefDefenderPlugin.DEFAULT_HOLDER) { + if (playerData != null) { + playerData.ignoreActiveContexts = true; + } + //contexts.addAll(PermissionUtil.getInstance().getActiveContexts(holder)); + PermissionUtil.getInstance().addActiveContexts(contexts, holder, playerData, claim); + } + + final String value = PermissionUtil.getInstance().getOptionValue(holder, option, contexts); + if (value != null) { + return this.getDoubleValue(value); + } + + return Double.valueOf(option.getDefaultValue().toString()); + } + + private Double getDoubleValue(String option) { + if (option == null) { + return null; + } + + double optionValue = 0.0; + try { + optionValue = Double.parseDouble(option); + } catch (NumberFormatException e) { + + } + return optionValue; + } + + public Optional<Flag> getFlag(String value) { + if (value == null) { + return Optional.empty(); + } + + value = value.replace("griefdefender.flag.", ""); + String[] parts = value.split("\\."); + if (parts.length > 0) { + value = parts[0]; + } + + return FlagRegistryModule.getInstance().getById(value); + } + + public Optional<Option> getOption(String value) { + if (value == null) { + return Optional.empty(); + } + + value = value.replace("griefdefender.", ""); + String[] parts = value.split("\\."); + if (parts.length > 0) { + value = parts[0]; + } + + return GriefDefender.getRegistry().getType(Option.class, value); + } + + public Component getEventMessage() { + return this.eventMessage; + } + + @Override + public CompletableFuture<PermissionResult> clearOptions() { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture<PermissionResult> clearOptions(Set<Context> contexts) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Tristate getFlagPermissionValue(Flag flag, Subject subject, Set<Context> contexts) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture<PermissionResult> setFlagPermission(Flag flag, Subject subject, Tristate value, + Set<Context> contexts) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture<PermissionResult> setOption(Option option, String value, Set<Context> contexts) { + // TODO Auto-generated method stub + return null; + } + + @Override + public CompletableFuture<PermissionResult> setOption(Option option, Subject subject, String value, Set<Context> contexts) { + // TODO Auto-generated method stub + return null; + } + + @Override + public <T> Optional<T> getOptionValue(TypeToken<T> type, Option<T> option, Set<Context> contexts) { + String value = PermissionUtil.getInstance().getOptionValue(GriefDefenderPlugin.DEFAULT_HOLDER, option, contexts); + if (value != null) { + return Optional.of(this.getOptionTypeValue(type, value)); + } + + return Optional.empty(); + } + + @Override + public <T> Optional<T> getOptionValue(TypeToken<T> type, Subject subject, Option<T> option, Set<Context> contexts) { + String value = PermissionUtil.getInstance().getOptionValue((GDPermissionHolder) subject, option, contexts); + if (value != null) { + return Optional.of(this.getOptionTypeValue(type, value)); + } + + return Optional.empty(); + } + + @Override + public <T> T getActiveOptionValue(TypeToken<T> type, Option<T> option, Subject subject, Claim claim, + Set<Context> contexts) { + return this.getInternalOptionValue(type, (GDPermissionHolder) subject, option, claim, claim.getType(), contexts); + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissionResult.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissionResult.java new file mode 100644 index 0000000..f10d2d0 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissionResult.java @@ -0,0 +1,57 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.ResultType; +import net.kyori.text.Component; + +import java.util.Optional; + +public class GDPermissionResult implements PermissionResult { + + private final ResultType resultType; + private final Component eventMessage; + + public GDPermissionResult(ResultType type) { + this(type, null); + } + + public GDPermissionResult(ResultType type, Component message) { + this.resultType = type; + this.eventMessage = message; + } + + @Override + public ResultType getResultType() { + return this.resultType; + } + + @Override + public Optional<Component> getMessage() { + return Optional.ofNullable(this.eventMessage); + } + +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissionUser.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissionUser.java new file mode 100644 index 0000000..b9ae656 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissionUser.java @@ -0,0 +1,156 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.user.UserStorageService; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.User; +import com.griefdefender.api.data.PlayerData; + +import java.util.UUID; + +import org.checkerframework.checker.nullness.qual.Nullable; + +public class GDPermissionUser extends GDPermissionHolder implements User { + + private String userName; + private UUID uniqueId; + private UUID worldUniqueId; + private org.spongepowered.api.entity.living.player.User user; + private GDPlayerData playerData; + + public GDPermissionUser(Player player) { + super(player.getUniqueId().toString()); + this.uniqueId = player.getUniqueId(); + this.worldUniqueId = player.getWorld().getUniqueId(); + this.user = player; + this.userName = player.getName(); + } + + public GDPermissionUser(org.spongepowered.api.entity.living.player.User user) { + super(user.getUniqueId().toString()); + this.uniqueId = user.getUniqueId(); + this.userName = user.getName(); + this.user = user; + } + + public GDPermissionUser(UUID uuid, String objectName, String friendlyName) { + super(objectName); + this.uniqueId = uuid; + this.userName = objectName; + } + + public GDPermissionUser(UUID uuid) { + super(uuid.toString()); + this.uniqueId = uuid; + } + + // Used for Public/World user + public GDPermissionUser(UUID uuid, String name) { + super(uuid.toString()); + this.uniqueId = uuid; + this.userName = name; + } + + public String getName() { + if (this.userName == null) { + if (this.uniqueId.equals(GriefDefenderPlugin.PUBLIC_UUID)) { + this.userName = "public"; + } else if (this.uniqueId.equals(GriefDefenderPlugin.ADMIN_USER_UUID) || this.uniqueId.equals(GriefDefenderPlugin.WORLD_USER_UUID)) { + this.userName = "administrator"; + } else if (this.user != null) { + this.userName = this.user.getName(); + } else { + if (this.user == null) { + this.user = this.getOfflinePlayer(); + if (this.user != null) { + this.userName = this.user.getName(); + return this.userName; + } + } + // fallback to LP + this.userName = super.getFriendlyName(); + } + if (this.userName == null) { + this.userName = "unknown"; + } + } + + return this.userName; + } + + public String getFriendlyName() { + return this.getName(); + } + + @Nullable + public Player getOnlinePlayer() { + return Sponge.getServer().getPlayer(this.uniqueId).orElse(null); + } + + public org.spongepowered.api.entity.living.player.User getOfflinePlayer() { + final org.spongepowered.api.entity.living.player.User player = this.getOnlinePlayer(); + if (player != null) { + return player; + } + + if (this.user == null) { + this.user = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(this.uniqueId).orElse(null); + if (user != null) { + return user; + } + } + return this.user; + } + + @Override + public UUID getUniqueId() { + return this.uniqueId; + } + + @Override + public PlayerData getPlayerData() { + if (this.playerData == null) { + if (this.worldUniqueId != null) { + this.playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(this.worldUniqueId, this.uniqueId); + } else { + this.playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreateGlobalPlayerData(this.uniqueId); + } + } + return this.playerData; + } + + public GDPlayerData getInternalPlayerData() { + return (GDPlayerData) this.getPlayerData(); + } + + @Override + public boolean isOnline() { + return this.getOnlinePlayer() != null; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDPermissions.java b/sponge/src/main/java/com/griefdefender/permission/GDPermissions.java new file mode 100644 index 0000000..8f98eb1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDPermissions.java @@ -0,0 +1,250 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission; + +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; + +public class GDPermissions { + + // Claims + public static final String COMMAND_ABANDON_BASIC = "griefdefender.user.claim.command.abandon.basic"; + public static final String COMMAND_ABANDON_TOWN = "griefdefender.user.claim.command.abandon.town"; + public static final String COMMAND_ABANDON_ALL_CLAIMS = "griefdefender.user.claim.command.abandon-all"; + public static final String COMMAND_ABANDON_TOP_LEVEL_CLAIM = "griefdefender.user.claim.command.abandon-top-level"; + public static final String COMMAND_CUBOID_CLAIMS = "griefdefender.user.claim.command.cuboid"; + public static final String COMMAND_CLAIM_LIST = "griefdefender.user.claim.command.list"; + public static final String COMMAND_BASIC_MODE = "griefdefender.user.claim.command.basic-mode"; + public static final String COMMAND_GIVE_BLOCKS = "griefdefender.user.claim.command.give.blocks"; + public static final String COMMAND_GIVE_BOOK = "griefdefender.user.claim.command.give.book"; + public static final String COMMAND_GIVE_PET = "griefdefender.user.claim.command.give.pet"; + public static final String COMMAND_CLAIM_BANK = "griefdefender.user.claim.command.bank"; + public static final String COMMAND_CLAIM_BUY = "griefdefender.user.claim.command.buy"; + public static final String COMMAND_CLAIM_CONTRACT = "griefdefender.user.claim.command.contract"; + public static final String COMMAND_CLAIM_EXPAND = "griefdefender.user.claim.command.expand"; + public static final String COMMAND_CLAIM_INFO_OTHERS = "griefdefender.user.claim.command.info.others"; + public static final String COMMAND_CLAIM_INFO_BASE = "griefdefender.user.claim.command.info.base"; + public static final String COMMAND_CLAIM_INFO_TELEPORT_OTHERS = "griefdefender.user.claim.command.info.teleport.others"; + public static final String COMMAND_CLAIM_INFO_TELEPORT_BASE = "griefdefender.user.claim.command.info.teleport.base"; + public static final String COMMAND_CLAIM_MODE = "griefdefender.user.claim.command.claim-mode"; + public static final String COMMAND_CLAIM_OPTIONS_BASE = "griefdefender.user.claim.option.base"; + public static final String COMMAND_CLAIM_SELL = "griefdefender.user.claim.command.sell"; + public static final String COMMAND_CLAIM_SPAWN = "griefdefender.user.claim.command.spawn"; + public static final String COMMAND_CLAIM_SET_SPAWN = "griefdefender.user.claim.command.set-spawn"; + public static final String COMMAND_CLAIM_TAX = "griefdefender.user.claim.command.claim.tax"; + public static final String COMMAND_CLAIM_WORLDEDIT = "griefdefender.user.claim.command.worldedit-claim"; + public static final String COMMAND_SET_CLAIM_NAME = "griefdefender.user.claim.command.name"; + public static final String COMMAND_SET_CLAIM_FAREWELL = "griefdefender.user.claim.command.farewell"; + public static final String COMMAND_SET_CLAIM_GREETING = "griefdefender.user.claim.command.greeting"; + public static final String COMMAND_SUBDIVIDE_CLAIMS = "griefdefender.user.claim.command.subdivide-mode"; + public static final String COMMAND_TOWN_BANK = "griefdefender.user.town.command.bank"; + public static final String COMMAND_TOWN_CHAT = "griefdefender.user.town.command.chat"; + public static final String COMMAND_TOWN_INFO_BASE = "griefdefender.user.town.command.info.base"; + public static final String COMMAND_TOWN_INFO_OTHERS = "griefdefender.user.town.command.info.others"; + public static final String COMMAND_TOWN_INFO_TELEPORT_OTHERS = "griefdefender.user.town.command.info.teleport.others"; + public static final String COMMAND_TOWN_INFO_TELEPORT_BASE = "griefdefender.user.town.command.info.teleport.base"; + public static final String COMMAND_TOWN_NAME = "griefdefender.user.town.command.name"; + public static final String COMMAND_TOWN_TAG = "griefdefender.user.town.command.tag"; + public static final String COMMAND_TOWN_TAX = "griefdefender.user.town.command.tax"; + public static final String COMMAND_TOWN_MODE = "griefdefender.user.claim.command.town-mode"; + public static final String COMMAND_TRANSFER_CLAIM = "griefdefender.user.claim.command.transfer"; + public static final String COMMAND_BUY_CLAIM_BLOCKS = "griefdefender.user.claim.command.buy-blocks"; + public static final String COMMAND_SELL_CLAIM_BLOCKS = "griefdefender.user.claim.command.sell-blocks"; + public static final String COMMAND_LIST_CLAIM_FLAGS = "griefdefender.user.claim.command.list-flags"; + public static final String COMMAND_LIST_CLAIM_OPTIONS = "griefdefender.user.claim.command.list-options"; + public static final String COMMAND_BAN_ITEM = "griefdefender.user.claim.command.ban-item"; + public static final String COMMAND_UNBAN_ITEM = "griefdefender.user.claim.command.unban-item"; + public static final String COMMAND_CLAIM_INHERIT = "griefdefender.user.claim.command.inherit"; + public static final String CLAIM_CREATE = "griefdefender.user.claim.create.base"; + public static final String CLAIM_CREATE_BASIC = "griefdefender.user.claim.create.basic"; + public static final String CLAIM_CREATE_SUBDIVISION = "griefdefender.user.claim.create.subdivision"; + public static final String CLAIM_CREATE_TOWN = "griefdefender.user.claim.create.town"; + public static final String CLAIM_CUBOID_BASIC = "griefdefender.user.claim.create.cuboid.basic"; + public static final String CLAIM_CUBOID_SUBDIVISION = "griefdefender.user.claim.create.cuboid.subdivision"; + public static final String CLAIM_CUBOID_TOWN = "griefdefender.user.claim.create.cuboid.town"; + public static final String CLAIM_PVP_OVERRIDE = "griefdefender.user.claim.pvp-override"; + public static final String CLAIM_RESIZE = "griefdefender.user.claim.resize"; + public static final String CLAIM_SHOW_TUTORIAL = "griefdefender.user.claim.show-tutorial"; + public static final String LIST_OTHER_CLAIMS = "griefdefender.user.claim.list.other"; + public static final String VISUALIZE_CLAIMS = "griefdefender.user.claim.visualize.base"; + public static final String VISUALIZE_CLAIMS_NEARBY = "griefdefender.user.claim.visualize.nearby"; + public static final String COMMAND_PLAYER_INFO_BASE = "griefdefender.user.command.info.base"; + public static final String COMMAND_PLAYER_INFO_OTHERS = "griefdefender.user.command.info.others"; + public static final String COMMAND_VERSION = "griefdefender.user.command.version"; + + // flags + public static final String USER_CLAIM_FLAGS = "griefdefender.user.claim.flag"; + public static final String COMMAND_FLAGS_CLAIM = "griefdefender.user.claim.command.flag.base"; + public static final String COMMAND_FLAGS_DEBUG = "griefdefender.user.claim.command.flag.debug"; + public static final String COMMAND_FLAGS_PLAYER = "griefdefender.user.claim.command.flag.player"; + public static final String COMMAND_FLAGS_GROUP = "griefdefender.user.claim.command.flag.group"; + public static final String COMMAND_FLAGS_RESET = "griefdefender.user.claim.command.flag.reset"; + + public static final String FLAG_CUSTOM_ADMIN_BASE = "griefdefender.admin.custom.flag"; + public static final String FLAG_CUSTOM_USER_BASE = "griefdefender.user.custom.flag"; + + // options + public static final String USER_CLAIM_OPTIONS = "griefdefender.user.claim.option"; + public static final String COMMAND_OPTIONS_CLAIM = "griefdefender.user.claim.command.option.base"; + public static final String COMMAND_OPTIONS_PLAYER = "griefdefender.user.claim.command.option.player"; + public static final String COMMAND_OPTIONS_GROUP = "griefdefender.user.claim.command.option.group"; + public static final String USER_OPTION_PERK_OWNER_FLY_BASIC = "griefdefender.user.option.perk.owner-fly.basic"; + public static final String USER_OPTION_PERK_OWNER_FLY_TOWN = "griefdefender.user.option.perk.owner-fly.town"; + public static final String OPTION_BASE = "griefdefender"; + + // Admin + public static final String ADVANCED_FLAGS = "griefdefender.admin.advanced-flags"; + public static final String BYPASS_BAN = "griefdefender.admin.bypass.ban"; + public static final String BYPASS_BORDER_CHECK = "griefdefender.admin.bypass.border-check"; + public static final String BYPASS_CLAIM_RESIZE = "griefdefender.admin.bypass.override.resize"; + public static final String BYPASS_CLAIM_LIMIT = "griefdefender.admin.bypass.override.limit"; + public static final String BYPASS_OPTION = "griefdefender.admin.bypass.option"; + public static final String CLAIM_CUBOID_ADMIN = "griefdefender.admin.claim.cuboid"; + public static final String CLAIM_RESIZE_ALL = "griefdefender.admin.claim.resize"; + public static final String CLAIM_RESIZE_ADMIN = "griefdefender.admin.claim.resize.admin"; + public static final String CLAIM_RESIZE_ADMIN_SUBDIVISION = "griefdefender.admin.claim.resize.admin.subdivision"; + public static final String CLAIM_RESIZE_BASIC = "griefdefender.admin.claim.resize.basic"; + public static final String CLAIM_RESIZE_BASIC_SUBDIVISION = "griefdefender.admin.claim.resize.basic.subdivision"; + public static final String CLAIM_RESIZE_TOWN = "griefdefender.admin.claim.resize.town"; + public static final String COMMAND_ADJUST_CLAIM_BLOCKS = "griefdefender.admin.claim.command.adjust-claim-blocks"; + public static final String COMMAND_ADMIN_CLAIMS = "griefdefender.admin.claim.command.admin-mode"; + public static final String COMMAND_ADMIN_DEBUG = "griefdefender.admin.claim.command.debug"; + public static final String COMMAND_CLAIM_BAN = "griefdefender.admin.claim.command.ban"; + public static final String COMMAND_CLAIM_CLEAR = "griefdefender.admin.claim.command.clear"; + public static final String COMMAND_CLAIM_PERMISSION_GROUP = "griefdefender.admin.claim.command.permission-group"; + public static final String COMMAND_CLAIM_PERMISSION_PLAYER = "griefdefender.admin.claim.command.permission-player"; + public static final String COMMAND_CLAIM_SCHEMATIC = "griefdefender.admin.claim.command.schematic"; + public static final String COMMAND_CLAIM_OPTIONS_GROUP_BASE = "griefdefender.admin.claim.command.option.group.base"; + public static final String COMMAND_CLAIM_OPTIONS_GROUP_ADMIN = "griefdefender.admin.claim.command.option.group.admin"; + public static final String COMMAND_CLAIM_OPTIONS_GROUP_BASIC = "griefdefender.admin.claim.command.option.group.basic"; + public static final String COMMAND_CLAIM_OPTIONS_GROUP_SUBDIVISION = "griefdefender.admin.claim.command.option.group.subdivision"; + public static final String COMMAND_CLAIM_OPTIONS_GROUP_TOWN = "griefdefender.admin.claim.command.option.group.town"; + public static final String COMMAND_CLAIM_OPTIONS_PLAYER_BASE = "griefdefender.admin.claim.command.option.player.base"; + public static final String COMMAND_CLAIM_OPTIONS_PLAYER_ADMIN = "griefdefender.admin.claim.command.option.player.admin"; + public static final String COMMAND_CLAIM_OPTIONS_PLAYER_BASIC = "griefdefender.admin.claim.command.option.player.basic"; + public static final String COMMAND_CLAIM_OPTIONS_PLAYER_SUBDIVISION = "griefdefender.admin.claim.command.option.player.subdivision"; + public static final String COMMAND_CLAIM_OPTIONS_PLAYER_TOWN = "griefdefender.admin.claim.command.option.player.town"; + public static final String COMMAND_IGNORE_CLAIMS = "griefdefender.admin.claim.command.ignore.base"; + public static final String COMMAND_DELETE_CLAIM_BASE = "griefdefender.admin.claim.command.delete.base"; + public static final String COMMAND_DELETE_CLAIMS = "griefdefender.admin.claim.command.delete-claims"; + public static final String COMMAND_DELETE_ADMIN_CLAIMS = "griefdefender.admin.command.delete-admin-claims"; + public static final String COMMAND_SET_ACCRUED_CLAIM_BLOCKS = "griefdefender.admin.command.set-accrued-claim-blocks"; + public static final String COMMAND_RESTORE_CLAIM = "griefdefender.admin.command.restore-claim.base"; + public static final String COMMAND_RESTORE_NATURE = "griefdefender.admin.command.restore-nature.base"; + public static final String COMMAND_RESTORE_NATURE_AGGRESSIVE = "griefdefender.admin.command.restore-nature.aggressive"; + public static final String COMMAND_RESTORE_NATURE_FILL = "griefdefender.admin.command.restore-nature.fill"; + public static final String COMMAND_RELOAD = "griefdefender.admin.command.reload"; + public static final String DELETE_CLAIM_BASIC = "griefdefender.admin.claim.command.delete.basic"; + public static final String DELETE_CLAIM_ADMIN = "griefdefender.admin.claim.command.delete.admin"; + public static final String EAVES_DROP_SIGNS = "griefdefender.admin.eavesdrop.signs"; + public static final String IGNORE_CLAIMS_BASIC = "griefdefender.admin.claim.command.ignore.basic"; + public static final String IGNORE_CLAIMS_ADMIN = "griefdefender.admin.claim.command.ignore.admin"; + public static final String IGNORE_CLAIMS_TOWN = "griefdefender.admin.claim.command.ignore.town"; + public static final String IGNORE_CLAIMS_WILDERNESS = "griefdefender.admin.claim.command.ignore.wilderness"; + public static final String LIST_ADMIN_CLAIMS = "griefdefender.admin.claim.list.admin"; + public static final String MANAGE_FLAG_DEFAULTS = "griefdefender.admin.flag-defaults"; + public static final String MANAGE_FLAG_OVERRIDES = "griefdefender.admin.flag-overrides"; + public static final String MANAGE_WILDERNESS = "griefdefender.admin.claim.wilderness"; + public static final String MANAGE_ADMIN_OPTIONS = "griefdefender.admin.claim.option.admin"; + public static final String MANAGE_GLOBAL_OPTIONS = "griefdefender.admin.claim.option.global"; + public static final String MANAGE_OVERRIDE_OPTIONS = "griefdefender.admin.claim.option.override"; + public static final String SET_ADMIN_FLAGS = "griefdefender.admin.claim.set-admin-flags"; + + // Misc + public static final String COMMAND_HELP = "griefdefender.user.command.help"; + + // Trust + public static final String COMMAND_TRUST_GROUP = "griefdefender.user.claim.command.trust.group"; + public static final String COMMAND_TRUST_PLAYER = "griefdefender.user.claim.command.trust.player"; + public static final String COMMAND_LIST_TRUST = "griefdefender.user.claim.command.trust.list"; + public static final String COMMAND_TRUSTALL_GROUP = "griefdefender.user.claim.command.trustall.group"; + public static final String COMMAND_TRUSTALL_PLAYER = "griefdefender.user.claim.command.trustall.player"; + public static final String COMMAND_UNTRUST_GROUP = "griefdefender.user.claim.command.untrust.group"; + public static final String COMMAND_UNTRUST_PLAYER = "griefdefender.user.claim.command.untrust.player"; + public static final String COMMAND_UNTRUSTALL_GROUP = "griefdefender.user.claim.command.untrustall.group"; + public static final String COMMAND_UNTRUSTALL_PLAYER = "griefdefender.user.claim.command.untrustall.player"; + public static final String GIVE_ACCESS_TRUST = "griefdefender.user.claim.trust.accessor"; + public static final String GIVE_CONTAINER_TRUST = "griefdefender.user.claim.trust.container"; + public static final String GIVE_BUILDER_TRUST = "griefdefender.user.claim.trust.builder"; + public static final String GIVE_MANAGER_TRUST = "griefdefender.user.claim.trust.manager"; + public static final String REMOVE_TRUST = "griefdefender.user.claim.trust.remove"; + public static final String TRUST_ACCESSOR = "griefdefender.trust.1.2.3.4"; + public static final String TRUST_CONTAINER = "griefdefender.trust.1.2.3"; + public static final String TRUST_BUILDER = "griefdefender.trust.1.2"; + public static final String TRUST_MANAGER = "griefdefender.trust.1"; + + // Flags + public static final String BLOCK_BREAK = "griefdefender.flag.block-break"; + public static final String BLOCK_GROW = "griefdefender.flag.block-grow"; + public static final String BLOCK_MODIFY = "griefdefender.flag.block-modify"; + public static final String BLOCK_PLACE = "griefdefender.flag.block-place"; + public static final String BLOCK_SPREAD = "griefdefender.flag.block-spread"; + public static final String COLLIDE_BLOCK = "griefdefender.flag.collide-block"; + public static final String COLLIDE_ENTITY = "griefdefender.flag.collide-entity"; + public static final String COMMAND_EXECUTE = "griefdefender.flag.command-execute"; + public static final String COMMAND_EXECUTE_PVP = "griefdefender.flag.command-execute-pvp"; + public static final String ENTER_CLAIM = "griefdefender.flag.enter-claim"; + public static final String ENTITY_CHUNK_SPAWN = "griefdefender.flag.entity-chunk-spawn"; + public static final String ENTITY_DAMAGE = "griefdefender.flag.entity-damage"; + public static final String ENTITY_RIDING = "griefdefender.flag.entity-riding"; + public static final String ENTITY_SPAWN = "griefdefender.flag.entity-spawn"; + public static final String ENTITY_TELEPORT_FROM = "griefdefender.flag.entity-teleport-from"; + public static final String ENTITY_TELEPORT_TO = "griefdefender.flag.entity-teleport-to"; + public static final String EXIT_CLAIM = "griefdefender.flag.exit-claim"; + public static final String EXPLOSION_BLOCK = "griefdefender.flag.explosion-block"; + public static final String EXPLOSION_ENTITY = "griefdefender.flag.explosion-entity"; + public static final String FLAG_BASE = "griefdefender.flag"; + public static final String INTERACT_BLOCK_PRIMARY = "griefdefender.flag.interact-block-primary"; + public static final String INTERACT_BLOCK_SECONDARY = "griefdefender.flag.interact-block-secondary"; + public static final String INTERACT_ENTITY_PRIMARY = "griefdefender.flag.interact-entity-primary"; + public static final String INTERACT_ENTITY_SECONDARY = "griefdefender.flag.interact-entity-secondary"; + public static final String INTERACT_ITEM_PRIMARY = "griefdefender.flag.interact-item-primary"; + public static final String INTERACT_ITEM_SECONDARY = "griefdefender.flag.interact-item-secondary"; + public static final String INVENTORY_CLICK = "griefdefender.flag.interact-inventory-click"; + public static final String INVENTORY_OPEN = "griefdefender.flag.interact-inventory"; + public static final String ITEM_DROP = "griefdefender.flag.item-drop"; + public static final String ITEM_PICKUP = "griefdefender.flag.item-pickup"; + public static final String ITEM_SPAWN = "griefdefender.flag.item-spawn"; + public static final String ITEM_USE = "griefdefender.flag.item-use"; + public static final String LEAF_DECAY = "griefdefender.flag.leaf-decay"; + public static final String LIQUID_FLOW = "griefdefender.flag.liquid-flow"; + public static final String PORTAL_USE = "griefdefender.flag.portal-use"; + public static final String PROJECTILE_IMPACT_BLOCK = "griefdefender.flag.projectile-impact-block"; + public static final String PROJECTILE_IMPACT_ENTITY = "griefdefender.flag.projectile-impact-entity"; + + public static String getTrustPermission(TrustType type) { + if (type == TrustTypes.ACCESSOR) { + return GDPermissions.TRUST_ACCESSOR; + } + if (type == TrustTypes.BUILDER) { + return GDPermissions.TRUST_BUILDER; + } + if (type == TrustTypes.CONTAINER) { + return GDPermissions.TRUST_CONTAINER; + } + + return GDPermissions.TRUST_MANAGER; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/GDResultType.java b/sponge/src/main/java/com/griefdefender/permission/GDResultType.java new file mode 100644 index 0000000..d0668a3 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/GDResultType.java @@ -0,0 +1,31 @@ +package com.griefdefender.permission; + +import com.griefdefender.api.permission.ResultType; +import net.kyori.text.Component; + +public class GDResultType implements ResultType { + + private final String id; + private final String name; + private Component description; + + public GDResultType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Component getDescription() { + return this.description; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/CustomFlagData.java b/sponge/src/main/java/com/griefdefender/permission/flag/CustomFlagData.java new file mode 100644 index 0000000..9d98f13 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/CustomFlagData.java @@ -0,0 +1,65 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import java.util.Set; + +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; + +public class CustomFlagData { + + private Flag flag; + private Set<Context> contexts; + + public CustomFlagData(Flag flag, Set<Context> contexts) { + this.flag = flag; + this.contexts = contexts; + } + + public Set<Context> getContexts() { + return this.contexts; + } + + public Flag getFlag() { + return this.flag; + } + + public boolean matches(Flag otherFlag, Set<Context> otherContexts) { + for (Context context : this.contexts) { + boolean found = false; + for (Context other : otherContexts) { + if (other.getKey().equals(context.getKey())) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/FlagContexts.java b/sponge/src/main/java/com/griefdefender/permission/flag/FlagContexts.java new file mode 100644 index 0000000..047266a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/FlagContexts.java @@ -0,0 +1,82 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; + +public class FlagContexts { + + public static final Context SOURCE_PLAYER = new Context(ContextKeys.SOURCE, "minecraft:player"); + public static final Context SOURCE_TNT = new Context(ContextKeys.SOURCE, "minecraft:tnt"); + public static final Context SOURCE_CREEPER = new Context(ContextKeys.SOURCE, "minecraft:creeper"); + public static final Context SOURCE_ENDERDRAGON = new Context(ContextKeys.SOURCE, "minecraft:enderdragon"); + public static final Context SOURCE_GHAST = new Context(ContextKeys.SOURCE, "minecraft:ghast"); + public static final Context SOURCE_ENDERMAN = new Context(ContextKeys.SOURCE, "minecraft:enderman"); + public static final Context SOURCE_SNOWMAN = new Context(ContextKeys.SOURCE, "minecraft:snowman"); + public static final Context SOURCE_WITHER = new Context(ContextKeys.SOURCE, "minecraft:wither"); + public static final Context SOURCE_LAVA = new Context(ContextKeys.SOURCE, "minecraft:flowing_lava"); + public static final Context SOURCE_WATER = new Context(ContextKeys.SOURCE, "minecraft:flowing_water"); + public static final Context SOURCE_LIGHTNING_BOLT = new Context(ContextKeys.SOURCE, "minecraft:lightning_bolt"); + public static final Context SOURCE_FALL = new Context(ContextKeys.SOURCE, "minecraft:fall"); + public static final Context SOURCE_FIRE = new Context(ContextKeys.SOURCE, "minecraft:fire"); + public static final Context SOURCE_FIREWORKS = new Context(ContextKeys.SOURCE, "minecraft:fireworks"); + public static final Context SOURCE_PISTON = new Context(ContextKeys.TARGET, "minecraft:piston"); + public static final Context SOURCE_VINE = new Context(ContextKeys.SOURCE, "minecraft:vine"); + public static final Context SOURCE_TYPE_MONSTER = new Context(ContextKeys.SOURCE, "#monster"); + + // Block States + public static final Context STATE_FARMLAND_DRY = new Context("state", "moisture:0"); + + // Targets + public static final Context TARGET_BED = new Context(ContextKeys.TARGET, "minecraft:bed"); + public static final Context TARGET_BOAT = new Context(ContextKeys.TARGET, "minecraft:boat"); + public static final Context TARGET_CHEST = new Context(ContextKeys.TARGET, "minecraft:chest"); + public static final Context TARGET_CHORUS_FRUIT = new Context(ContextKeys.TARGET, "minecraft:chorus_fruit"); + public static final Context TARGET_ENDERPEARL = new Context(ContextKeys.TARGET, "minecraft:enderpearl"); + public static final Context TARGET_FARMLAND = new Context(ContextKeys.TARGET, "minecraft:farmland"); + public static final Context TARGET_FLINTANDSTEEL = new Context(ContextKeys.TARGET, "minecraft:flint_and_steel"); + public static final Context TARGET_GRASS= new Context(ContextKeys.TARGET, "minecraft:grass"); + public static final Context TARGET_ITEM_FRAME = new Context(ContextKeys.TARGET, "minecraft:item_frame"); + public static final Context TARGET_MINECART = new Context(ContextKeys.TARGET, "minecraft:minecart"); + public static final Context TARGET_MYCELIUM = new Context(ContextKeys.TARGET, "minecraft:mycelium"); + public static final Context TARGET_PAINTING = new Context(ContextKeys.TARGET, "minecraft:painting"); + public static final Context TARGET_PISTON = new Context(ContextKeys.TARGET, "minecraft:piston"); + public static final Context TARGET_PLAYER = new Context(ContextKeys.TARGET, "minecraft:player"); + public static final Context TARGET_ICE_FORM = new Context(ContextKeys.TARGET, "minecraft:ice"); + public static final Context TARGET_ICE_MELT = new Context(ContextKeys.TARGET, "minecraft:water"); + public static final Context TARGET_SNOW_LAYER = new Context(ContextKeys.TARGET, "minecraft:snow_layer"); + public static final Context TARGET_TURTLE_EGG = new Context(ContextKeys.TARGET, "minecraft:turtle_egg"); + public static final Context TARGET_VINE = new Context(ContextKeys.TARGET, "minecraft:vine"); + public static final Context TARGET_XP_ORB = new Context(ContextKeys.TARGET, "minecraft:xp_orb"); + public static final Context TARGET_TYPE_ANIMAL = new Context(ContextKeys.TARGET, "#animal"); + public static final Context TARGET_TYPE_CROP = new Context(ContextKeys.TARGET, "#crop"); + public static final Context TARGET_TYPE_AMBIENT = new Context(ContextKeys.TARGET, "#ambient"); + public static final Context TARGET_TYPE_AQUATIC = new Context(ContextKeys.TARGET, "#aquatic"); + public static final Context TARGET_TYPE_MONSTER = new Context(ContextKeys.TARGET, "#monster"); + public static final Context TARGET_TYPE_MUSHROOM = new Context(ContextKeys.TARGET, "#mushroom"); + public static final Context TARGET_TYPE_PORTAL = new Context(ContextKeys.TARGET, "#portal"); + public static final Context TARGET_TYPE_VEHICLE = new Context(ContextKeys.TARGET, "#vehicle"); +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/GDActiveFlagData.java b/sponge/src/main/java/com/griefdefender/permission/flag/GDActiveFlagData.java new file mode 100644 index 0000000..b556b4e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/GDActiveFlagData.java @@ -0,0 +1,94 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.Context; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +public class GDActiveFlagData { + + public enum Type { + CLAIM, + DEFAULT, + OVERRIDE, + UNDEFINED + } + + private final CustomFlagData flagData; + private final Tristate value; + private final Type type; + + public GDActiveFlagData(CustomFlagData flagData, Tristate value, Type type) { + this.flagData = flagData; + this.value = value; + this.type = type; + } + + public CustomFlagData getFlagData() { + return this.flagData; + } + + public Tristate getValue() { + return this.value; + } + + public TextColor getColor() { + if (this.type == Type.CLAIM) { + return TextColor.YELLOW; + } + if (this.type == Type.OVERRIDE) { + return TextColor.RED; + } + if (this.type == Type.DEFAULT) { + return TextColor.LIGHT_PURPLE; + } + return TextColor.GRAY; + } + + public Component getComponent() { + TextComponent.Builder contextBuilder = TextComponent.builder(); + int count = 0; + for (Context context : this.flagData.getContexts()) { + if (count > 0) { + contextBuilder.append(", "); + } + contextBuilder.append(context.getKey().replace("gd_claim_", "").replace("gd_claim", ""), TextColor.GREEN) + .append("=") + .append(context.getValue(), TextColor.GRAY); + } + TextComponent.Builder builder = TextComponent.builder(); + builder + .append(this.flagData.getFlag().getName().toLowerCase(), this.getColor()) + .append("=", TextColor.WHITE) + .append(this.value.toString().toLowerCase(), TextColor.GOLD) + .append(" ") + .append(contextBuilder.build()); + return builder.build(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinition.java b/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinition.java new file mode 100644 index 0000000..894fd1b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinition.java @@ -0,0 +1,108 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.category.CustomFlagGroupCategory; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; + +public class GDCustomFlagDefinition { + + private boolean enabled = true; + private Set<Context> definitionContexts = new HashSet<>(); + private List<CustomFlagData> data = new ArrayList<>(); + private String displayName; + private Tristate defaultValue = Tristate.UNDEFINED; + private Component description; + + public GDCustomFlagDefinition(Flag flag, Set<Context> contexts, String displayName, Component description) { + this.data.add(new CustomFlagData(flag, contexts)); + this.displayName = displayName; + this.description = description; + } + + public GDCustomFlagDefinition(List<CustomFlagData> flagData, String displayName, Component description) { + this.data = flagData; + this.displayName = displayName; + this.description = description; + } + + public void addFlagData(Flag flag, Set<Context> contexts) { + this.data.add(new CustomFlagData(flag, contexts)); + } + + public List<Flag> getFlags() { + List<Flag> flags = new ArrayList<>(); + for (CustomFlagData flagData : this.data) { + flags.add(flagData.getFlag()); + } + return flags; + } + + public List<CustomFlagData> getFlagData() { + return this.data; + } + + public Component getDescription() { + return this.description; + } + + public Set<Context> getDefinitionContexts() { + return this.definitionContexts; + } + + public void setDefinitionContexts(Set<Context> contexts) { + this.definitionContexts = contexts; + } + + public String getDisplayName() { + return this.displayName; + } + + public boolean isEnabled() { + return this.enabled; + } + + public void setDefaultValue(Tristate value) { + this.defaultValue = value; + } + + public void setIsEnabled(boolean val) { + this.enabled = val; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinitions.java b/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinitions.java new file mode 100644 index 0000000..4e4a1fd --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/GDCustomFlagDefinitions.java @@ -0,0 +1,415 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.event.GDCauseStackManager; + +public class GDCustomFlagDefinitions { + + // ADMIN + public static final GDCustomFlagDefinition BLOCK_BREAK; + public static final GDCustomFlagDefinition BLOCK_GROW; + public static final GDCustomFlagDefinition BLOCK_PLACE; + public static final GDCustomFlagDefinition BLOCK_SPREAD; + public static final GDCustomFlagDefinition ENDERPEARL; + public static final GDCustomFlagDefinition EXIT_PLAYER; + public static final GDCustomFlagDefinition EXPLOSION_BLOCK; + public static final GDCustomFlagDefinition EXPLOSION_ENTITY; + public static final GDCustomFlagDefinition EXP_DROP; + public static final GDCustomFlagDefinition FALL_DAMAGE; + public static final GDCustomFlagDefinition INTERACT_BLOCK; + public static final GDCustomFlagDefinition INTERACT_ENTITY; + public static final GDCustomFlagDefinition INTERACT_INVENTORY; + public static final GDCustomFlagDefinition INVINCIBLE; + public static final GDCustomFlagDefinition ITEM_DROP; + public static final GDCustomFlagDefinition ITEM_PICKUP; + public static final GDCustomFlagDefinition MONSTER_DAMAGE; + public static final GDCustomFlagDefinition PISTONS; + public static final GDCustomFlagDefinition PORTAL_USE; + public static final GDCustomFlagDefinition SPAWN_MONSTER; + public static final GDCustomFlagDefinition TELEPORT_FROM; + public static final GDCustomFlagDefinition TELEPORT_TO; + public static final GDCustomFlagDefinition USE; + public static final GDCustomFlagDefinition VEHICLE_DESTROY; + public static final GDCustomFlagDefinition WITHER_DAMAGE; + + // USER + public static final GDCustomFlagDefinition BLOCK_TRAMPLING; + public static final GDCustomFlagDefinition CHEST_ACCESS; + public static final GDCustomFlagDefinition CHORUS_FRUIT_TELEPORT; + public static final GDCustomFlagDefinition CROP_GROWTH; + public static final GDCustomFlagDefinition DAMAGE_ANIMALS; + public static final GDCustomFlagDefinition ENDERMAN_GRIEF; + public static final GDCustomFlagDefinition ENTER_PLAYER; + public static final GDCustomFlagDefinition EXPLOSION_CREEPER; + public static final GDCustomFlagDefinition EXPLOSION_TNT; + public static final GDCustomFlagDefinition FIRE_DAMAGE; + public static final GDCustomFlagDefinition FIRE_SPREAD; + public static final GDCustomFlagDefinition GRASS_GROWTH; + public static final GDCustomFlagDefinition ICE_FORM; + public static final GDCustomFlagDefinition ICE_MELT; + public static final GDCustomFlagDefinition LAVA_FLOW; + public static final GDCustomFlagDefinition LEAF_DECAY; + public static final GDCustomFlagDefinition LIGHTNING; + public static final GDCustomFlagDefinition LIGHTER; + public static final GDCustomFlagDefinition MUSHROOM_GROWTH; + public static final GDCustomFlagDefinition MYCELIUM_SPREAD; + public static final GDCustomFlagDefinition PVP; + public static final GDCustomFlagDefinition RIDE; + public static final GDCustomFlagDefinition SLEEP; + public static final GDCustomFlagDefinition SNOW_FALL; + public static final GDCustomFlagDefinition SNOW_MELT; + public static final GDCustomFlagDefinition SNOWMAN_TRAIL; + public static final GDCustomFlagDefinition SOIL_DRY; + public static final GDCustomFlagDefinition SPAWN_AMBIENT; + public static final GDCustomFlagDefinition SPAWN_ANIMAL; + public static final GDCustomFlagDefinition SPAWN_AQUATIC; + public static final GDCustomFlagDefinition VEHICLE_DESTROY_CLAIM; + public static final GDCustomFlagDefinition VEHICLE_PLACE; + public static final GDCustomFlagDefinition VINE_GROWTH; + public static final GDCustomFlagDefinition WATER_FLOW; + + public static final List<GDCustomFlagDefinition> ADMIN_FLAGS = new ArrayList<>(); + public static final List<GDCustomFlagDefinition> USER_FLAGS = new ArrayList<>(); + + static { + Set<Context> contexts = new HashSet<>(); + + contexts = new HashSet<>(); + BLOCK_BREAK = new GDCustomFlagDefinition(Flags.BLOCK_BREAK, contexts, "block-break", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_BLOCK_BREAK); + + contexts = new HashSet<>(); + BLOCK_PLACE = new GDCustomFlagDefinition(Flags.BLOCK_PLACE, contexts, "block-place", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_BLOCK_PLACE); + + contexts = new HashSet<>(); + BLOCK_GROW = new GDCustomFlagDefinition(Flags.BLOCK_GROW, contexts, "block-grow", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_BLOCK_GROW); + + contexts = new HashSet<>(); + BLOCK_SPREAD = new GDCustomFlagDefinition(Flags.BLOCK_SPREAD, contexts, "block-spread", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_BLOCK_SPREAD); + + // ADMIN + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_ENDERMAN); + ENDERPEARL = new GDCustomFlagDefinition(Flags.INTERACT_ITEM_SECONDARY, contexts, "enderpearl", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ENDERPEARL); + ENDERPEARL.getDefinitionContexts().add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + ENTER_PLAYER = new GDCustomFlagDefinition(Flags.ENTER_CLAIM, contexts, "enter-player", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ENTER_PLAYER); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + EXIT_PLAYER = new GDCustomFlagDefinition(Flags.ENTER_CLAIM, contexts, "exit-player", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXIT_PLAYER); + + contexts = new HashSet<>(); + EXPLOSION_BLOCK = new GDCustomFlagDefinition(Flags.EXPLOSION_BLOCK, contexts, "explosion-block", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXPLOSION_BLOCK); + EXPLOSION_BLOCK.getDefinitionContexts().add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_CREEPER); + EXPLOSION_CREEPER = new GDCustomFlagDefinition(Flags.EXPLOSION_BLOCK, contexts, "explosion-creeper", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXPLOSION_CREEPER); + EXPLOSION_CREEPER.addFlagData(Flags.EXPLOSION_ENTITY, contexts); + + contexts = new HashSet<>(); + EXPLOSION_ENTITY = new GDCustomFlagDefinition(Flags.EXPLOSION_ENTITY, contexts, "explosion-entity", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXPLOSION_ENTITY); + EXPLOSION_ENTITY.getDefinitionContexts().add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_TNT); + EXPLOSION_TNT = new GDCustomFlagDefinition(Flags.EXPLOSION_BLOCK, contexts, "explosion-tnt", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXPLOSION_TNT); + EXPLOSION_TNT.addFlagData(Flags.EXPLOSION_ENTITY, contexts); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_XP_ORB); + EXP_DROP = new GDCustomFlagDefinition(Flags.ENTITY_SPAWN, contexts, "exp-drop", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_EXP_DROP); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_FALL); + contexts.add(FlagContexts.TARGET_PLAYER); + FALL_DAMAGE = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "fall-damage", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_FALL_DAMAGE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + INTERACT_BLOCK = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "interact-block", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_INTERACT_BLOCK); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + INTERACT_ENTITY = new GDCustomFlagDefinition(Flags.INTERACT_ENTITY_SECONDARY, contexts, "interact-entity", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_INTERACT_ENTITY); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + INTERACT_INVENTORY = new GDCustomFlagDefinition(Flags.INTERACT_INVENTORY, contexts, "interact-inventory", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_INTERACT_INVENTORY); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_PLAYER); + INVINCIBLE = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "invincible", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_INVINCIBLE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + ITEM_DROP = new GDCustomFlagDefinition(Flags.ITEM_DROP, contexts, "item-drop", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ITEM_DROP); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + ITEM_PICKUP = new GDCustomFlagDefinition(Flags.ITEM_PICKUP, contexts, "item-pickup", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ITEM_PICKUP); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_TYPE_MONSTER); + MONSTER_DAMAGE = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "monster-damage", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_MONSTER_DAMAGE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_PISTON); + PISTONS = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "pistons", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_PISTONS); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + contexts.add(FlagContexts.TARGET_TYPE_PORTAL); + PORTAL_USE = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "portal-use", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_PORTAL_USE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_MONSTER); + SPAWN_MONSTER = new GDCustomFlagDefinition(Flags.ENTITY_SPAWN, contexts, "spawn-monster", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SPAWN_MONSTER); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_PLAYER); + TELEPORT_FROM = new GDCustomFlagDefinition(Flags.ENTITY_TELEPORT_FROM, contexts, "teleport-from", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_TELEPORT_FROM); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_PLAYER); + TELEPORT_TO = new GDCustomFlagDefinition(Flags.ENTITY_TELEPORT_TO, contexts, "teleport-to", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_TELEPORT_TO); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_VEHICLE); + VEHICLE_DESTROY = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "vehicle-destroy", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_VEHICLE_DESTROY); + VEHICLE_DESTROY.getDefinitionContexts().add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_WITHER); + WITHER_DAMAGE = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "wither-damage", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_WITHER_DAMAGE); + WITHER_DAMAGE.getDefinitionContexts().add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + + ADMIN_FLAGS.add(BLOCK_BREAK); + ADMIN_FLAGS.add(BLOCK_PLACE); + ADMIN_FLAGS.add(BLOCK_GROW); + ADMIN_FLAGS.add(BLOCK_SPREAD); + ADMIN_FLAGS.add(ENDERPEARL); + ADMIN_FLAGS.add(ENTER_PLAYER); + ADMIN_FLAGS.add(EXIT_PLAYER); + ADMIN_FLAGS.add(EXPLOSION_BLOCK); + ADMIN_FLAGS.add(EXPLOSION_CREEPER); + ADMIN_FLAGS.add(EXPLOSION_ENTITY); + ADMIN_FLAGS.add(EXPLOSION_TNT); + ADMIN_FLAGS.add(EXP_DROP); + ADMIN_FLAGS.add(FALL_DAMAGE); + ADMIN_FLAGS.add(INTERACT_BLOCK); + ADMIN_FLAGS.add(INTERACT_ENTITY); + ADMIN_FLAGS.add(INTERACT_INVENTORY); + ADMIN_FLAGS.add(INVINCIBLE); + ADMIN_FLAGS.add(ITEM_DROP); + ADMIN_FLAGS.add(ITEM_PICKUP); + ADMIN_FLAGS.add(MONSTER_DAMAGE); + ADMIN_FLAGS.add(PISTONS); + ADMIN_FLAGS.add(SPAWN_MONSTER); + ADMIN_FLAGS.add(TELEPORT_FROM); + ADMIN_FLAGS.add(TELEPORT_TO); + ADMIN_FLAGS.add(VEHICLE_DESTROY); + ADMIN_FLAGS.add(WITHER_DAMAGE); + + + // USER + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_FARMLAND); + contexts.add(FlagContexts.TARGET_TURTLE_EGG); + BLOCK_TRAMPLING = new GDCustomFlagDefinition(Flags.COLLIDE_BLOCK, contexts, "block-trampling", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_BLOCK_TRAMPLING); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + contexts.add(FlagContexts.TARGET_CHEST); + CHEST_ACCESS = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "chest-access", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_CHEST_ACCESS); + CHEST_ACCESS.addFlagData(Flags.INTERACT_INVENTORY, contexts); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_CHORUS_FRUIT); + CHORUS_FRUIT_TELEPORT = new GDCustomFlagDefinition(Flags.INTERACT_ITEM_SECONDARY, contexts, "chorus-fruit-teleport", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_CHORUS_FRUIT_TELEPORT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_CROP); + CROP_GROWTH = new GDCustomFlagDefinition(Flags.BLOCK_GROW, contexts, "crop-growth", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_CROP_GROWTH); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_ANIMAL); + DAMAGE_ANIMALS = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "damage-animals", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_DAMAGE_ANIMALS); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_ENDERMAN); + ENDERMAN_GRIEF = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "enderman-grief", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ENDERMAN_GRIEF); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_FIRE); + FIRE_DAMAGE = new GDCustomFlagDefinition(Flags.BLOCK_MODIFY, contexts, "fire-damage", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_FIRE_DAMAGE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_FIRE); + FIRE_SPREAD = new GDCustomFlagDefinition(Flags.BLOCK_SPREAD, contexts, "fire-spread", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_FIRE_SPREAD); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_GRASS); + GRASS_GROWTH = new GDCustomFlagDefinition(Flags.BLOCK_GROW, contexts, "grass-growth", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_GRASS_GROWTH); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_ICE_FORM); + ICE_FORM = new GDCustomFlagDefinition(Flags.BLOCK_MODIFY, contexts, "ice-form", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ICE_FORM); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_ICE_MELT); + ICE_MELT = new GDCustomFlagDefinition(Flags.BLOCK_MODIFY, contexts, "ice-melt", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_ICE_MELT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_LAVA); + LAVA_FLOW = new GDCustomFlagDefinition(Flags.LIQUID_FLOW, contexts, "lava-flow", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_LAVA_FLOW); + + contexts = new HashSet<>(); + LEAF_DECAY = new GDCustomFlagDefinition(Flags.LEAF_DECAY, contexts, "leaf-decay", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_LEAF_DECAY); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_LIGHTNING_BOLT); + LIGHTNING = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "lightning", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_LIGHTNING); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_FLINTANDSTEEL); + LIGHTER = new GDCustomFlagDefinition(Flags.INTERACT_ITEM_SECONDARY, contexts, "lighter", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_LIGHTER); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_MUSHROOM); + MUSHROOM_GROWTH = new GDCustomFlagDefinition(Flags.BLOCK_GROW, contexts, "mushroom-growth", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_MUSHROOM_GROWTH); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_MYCELIUM); + MYCELIUM_SPREAD = new GDCustomFlagDefinition(Flags.BLOCK_SPREAD, contexts, "mycelium-spread", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_MYCELIUM_SPREAD); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + contexts.add(FlagContexts.TARGET_PLAYER); + PVP = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "pvp", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_PVP); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + contexts.add(FlagContexts.TARGET_TYPE_VEHICLE); + RIDE = new GDCustomFlagDefinition(Flags.ENTITY_RIDING, contexts, "ride", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_RIDE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + contexts.add(FlagContexts.TARGET_BED); + SLEEP = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "sleep", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SLEEP); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_SNOW_LAYER); + SNOW_FALL = new GDCustomFlagDefinition(Flags.BLOCK_PLACE, contexts, "snow-fall", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SNOW_FALL); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_SNOW_LAYER); + SNOW_MELT = new GDCustomFlagDefinition(Flags.BLOCK_BREAK, contexts, "snow-melt", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SNOW_MELT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_SNOWMAN); + SNOWMAN_TRAIL = new GDCustomFlagDefinition(Flags.BLOCK_MODIFY, contexts, "snowman-trail", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SNOWMAN_TRAIL); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.STATE_FARMLAND_DRY); + SOIL_DRY = new GDCustomFlagDefinition(Flags.BLOCK_MODIFY, contexts, "soil-dry", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SOIL_DRY); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_AMBIENT); + SPAWN_AMBIENT = new GDCustomFlagDefinition(Flags.ENTITY_SPAWN, contexts, "spawn-ambient", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SPAWN_AMBIENT); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_ANIMAL); + SPAWN_ANIMAL = new GDCustomFlagDefinition(Flags.ENTITY_SPAWN, contexts, "spawn-animal", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SPAWN_ANIMAL); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_AQUATIC); + SPAWN_AQUATIC = new GDCustomFlagDefinition(Flags.ENTITY_SPAWN, contexts, "spawn-aquatic", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_SPAWN_AQUATIC); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_PLAYER); + USE = new GDCustomFlagDefinition(Flags.INTERACT_BLOCK_SECONDARY, contexts, "use", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_USE); + USE.addFlagData(Flags.INTERACT_ENTITY_SECONDARY, new HashSet<>(contexts)); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_VEHICLE); + VEHICLE_DESTROY_CLAIM = new GDCustomFlagDefinition(Flags.ENTITY_DAMAGE, contexts, "vehicle-destroy", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_VEHICLE_DESTROY); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_TYPE_VEHICLE); + VEHICLE_PLACE = new GDCustomFlagDefinition(Flags.BLOCK_PLACE, contexts, "vehicle-place", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_VEHICLE_PLACE); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.TARGET_VINE); + VINE_GROWTH = new GDCustomFlagDefinition(Flags.BLOCK_GROW, contexts, "vine-growth", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_VINE_GROWTH); + + contexts = new HashSet<>(); + contexts.add(FlagContexts.SOURCE_WATER); + WATER_FLOW = new GDCustomFlagDefinition(Flags.LIQUID_FLOW, contexts, "water-flow", MessageCache.getInstance().FLAG_DESCRIPTION_CUSTOM_WATER_FLOW); + + USER_FLAGS.add(CHEST_ACCESS); + USER_FLAGS.add(CHORUS_FRUIT_TELEPORT); + USER_FLAGS.add(CROP_GROWTH); + USER_FLAGS.add(DAMAGE_ANIMALS); + USER_FLAGS.add(ENDERMAN_GRIEF); + USER_FLAGS.add(FIRE_DAMAGE); + USER_FLAGS.add(FIRE_SPREAD); + USER_FLAGS.add(GRASS_GROWTH); + USER_FLAGS.add(ICE_FORM); + USER_FLAGS.add(ICE_MELT); + USER_FLAGS.add(LAVA_FLOW); + USER_FLAGS.add(LEAF_DECAY); + USER_FLAGS.add(LIGHTER); + USER_FLAGS.add(LIGHTNING); + USER_FLAGS.add(MYCELIUM_SPREAD); + USER_FLAGS.add(PVP); + USER_FLAGS.add(RIDE); + USER_FLAGS.add(SLEEP); + USER_FLAGS.add(SNOW_FALL); + USER_FLAGS.add(SNOW_MELT); + USER_FLAGS.add(SOIL_DRY); + USER_FLAGS.add(SPAWN_AMBIENT); + USER_FLAGS.add(SPAWN_ANIMAL); + USER_FLAGS.add(SPAWN_AQUATIC); + USER_FLAGS.add(USE); + USER_FLAGS.add(VEHICLE_DESTROY_CLAIM); + USER_FLAGS.add(VEHICLE_PLACE); + USER_FLAGS.add(WATER_FLOW); + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/GDFlag.java b/sponge/src/main/java/com/griefdefender/permission/flag/GDFlag.java new file mode 100644 index 0000000..f6baa5b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/GDFlag.java @@ -0,0 +1,119 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.flag.Flag; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; + +public class GDFlag implements Flag { + + private final String id; + private final String name; + private Component description; + + public GDFlag(String id, String name) { + this.id = id; + this.name = name.toLowerCase(); + } + + @Override + public String getId() { + return this.id; + } + + public String getPermission() { + return "griefdefender.flag." + this.name.toLowerCase(); + } + + public String getName() { + return this.name; + } + + public Component getDescription() { + if (this.description == null) { + this.description = this.createDescription(); + } + return this.description; + } + + @Override + public String toString() { + return this.name; + } + + private Component createDescription() { + final Component description = GriefDefenderPlugin.getInstance().messageData.getMessage("flag-description-" + this.name.toLowerCase()); + if (description != null) { + return description; + } + return TextComponent.of("Not defined."); + } + + public void reloadDescription() { + this.description = null; + } + + @Override + public boolean getDefaultClaimTypeValue(ClaimType type) { + if (type == null || type != ClaimTypes.WILDERNESS) { + switch (this.name) { + case "block-break" : + case "block-modify" : + case "block-place" : + case "collide-block" : + case "collide-entity" : + case "entity-damage" : + case "explosion-block" : + case "explosion-entity" : + case "fire-spread" : + case "interact-block-primary" : + case "interact-block-secondary" : + case "interact-entity-primary" : + case "interact-inventory" : + case "liquid-flow" : + case "projectile-impact-block" : + case "projectile-impact-entity" : + return false; + default : + return true; + } + } + if (type == ClaimTypes.WILDERNESS) { + switch (this.name) { + case "fire-spread" : + return false; + + default : + return true; + } + } + + return true; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/flag/GDFlags.java b/sponge/src/main/java/com/griefdefender/permission/flag/GDFlags.java new file mode 100644 index 0000000..3b7ba2c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/flag/GDFlags.java @@ -0,0 +1,107 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.flag; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.flag.Flags; + +public class GDFlags { + + public static boolean BLOCK_BREAK; + public static boolean BLOCK_GROW; + public static boolean BLOCK_MODIFY; + public static boolean BLOCK_PLACE; + public static boolean BLOCK_SPREAD; + public static boolean COLLIDE_BLOCK; + public static boolean COLLIDE_ENTITY; + public static boolean COMMAND_EXECUTE; + public static boolean COMMAND_EXECUTE_PVP; + public static boolean ENTER_CLAIM; + public static boolean ENTITY_CHUNK_SPAWN; + public static boolean ENTITY_DAMAGE; + public static boolean ENTITY_RIDING; + public static boolean ENTITY_SPAWN; + public static boolean ENTITY_TELEPORT_FROM; + public static boolean ENTITY_TELEPORT_TO; + public static boolean EXIT_CLAIM; + public static boolean EXPLOSION_BLOCK; + public static boolean EXPLOSION_ENTITY; + public static boolean INTERACT_BLOCK_PRIMARY; + public static boolean INTERACT_BLOCK_SECONDARY; + public static boolean INTERACT_ENTITY_PRIMARY; + public static boolean INTERACT_ENTITY_SECONDARY; + public static boolean INTERACT_ITEM_PRIMARY; + public static boolean INTERACT_ITEM_SECONDARY; + public static boolean INTERACT_INVENTORY; + public static boolean INTERACT_INVENTORY_CLICK; + public static boolean ITEM_DROP; + public static boolean ITEM_PICKUP; + public static boolean ITEM_SPAWN; + public static boolean ITEM_USE; + public static boolean LEAF_DECAY; + public static boolean LIQUID_FLOW; + public static boolean PORTAL_USE; + public static boolean PROJECTILE_IMPACT_BLOCK; + public static boolean PROJECTILE_IMPACT_ENTITY; + + public static void populateFlagStatus() { + BLOCK_BREAK = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.BLOCK_BREAK.getName()); + BLOCK_GROW = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.BLOCK_GROW.getName()); + BLOCK_MODIFY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.BLOCK_MODIFY.getName()); + BLOCK_PLACE = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.BLOCK_PLACE.getName()); + BLOCK_SPREAD = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.BLOCK_SPREAD.getName()); + COLLIDE_BLOCK = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.COLLIDE_BLOCK.getName()); + COLLIDE_ENTITY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.COLLIDE_ENTITY.getName()); + COMMAND_EXECUTE = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.COMMAND_EXECUTE.getName()); + COMMAND_EXECUTE_PVP = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.COMMAND_EXECUTE_PVP.getName()); + ENTER_CLAIM = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTER_CLAIM.getName()); + ENTITY_CHUNK_SPAWN = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_CHUNK_SPAWN.getName()); + ENTITY_DAMAGE = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_DAMAGE.getName()); + ENTITY_RIDING = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_RIDING.getName()); + ENTITY_SPAWN = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_SPAWN.getName()); + ENTITY_TELEPORT_FROM = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_TELEPORT_FROM.getName()); + ENTITY_TELEPORT_TO = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ENTITY_TELEPORT_TO.getName()); + EXIT_CLAIM = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.EXIT_CLAIM.getName()); + EXPLOSION_BLOCK = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.EXPLOSION_BLOCK.getName()); + EXPLOSION_ENTITY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.EXPLOSION_ENTITY.getName()); + INTERACT_BLOCK_PRIMARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_BLOCK_PRIMARY.getName()); + INTERACT_BLOCK_SECONDARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_BLOCK_SECONDARY.getName()); + INTERACT_ENTITY_PRIMARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_ENTITY_PRIMARY.getName()); + INTERACT_ENTITY_SECONDARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_ENTITY_SECONDARY.getName()); + INTERACT_INVENTORY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_INVENTORY.getName()); + INTERACT_INVENTORY_CLICK = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_INVENTORY_CLICK.getName()); + INTERACT_ITEM_PRIMARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_ITEM_PRIMARY.getName()); + INTERACT_ITEM_SECONDARY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.INTERACT_ITEM_SECONDARY.getName()); + ITEM_DROP = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ITEM_DROP.getName()); + ITEM_PICKUP = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ITEM_PICKUP.getName()); + ITEM_SPAWN = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ITEM_SPAWN.getName()); + ITEM_USE = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.ITEM_USE.getName()); + LEAF_DECAY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.LEAF_DECAY.getName()); + LIQUID_FLOW = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.LIQUID_FLOW.getName()); + PORTAL_USE = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.PORTAL_USE.getName()); + PROJECTILE_IMPACT_BLOCK = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.PROJECTILE_IMPACT_BLOCK.getName()); + PROJECTILE_IMPACT_ENTITY = GriefDefenderPlugin.getGlobalConfig().getConfig().modules.isProtectionModuleEnabled(Flags.PROJECTILE_IMPACT_ENTITY.getName()); + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/option/GDOption.java b/sponge/src/main/java/com/griefdefender/permission/option/GDOption.java new file mode 100644 index 0000000..5f97884 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/option/GDOption.java @@ -0,0 +1,243 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.option; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.GameModeTypes; +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.api.permission.option.type.WeatherTypes; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; + +import java.util.Arrays; +import java.util.List; + +import org.checkerframework.checker.nullness.qual.Nullable; + +public class GDOption<T> implements Option<T> { + + private static final List<String> GLOBAL_OPTIONS = Arrays.asList( + "abandon-return-ratio", "blocks-accrued-per-hour", "chest-expiration", "economy-block-cost", + "economy-block-sell-return", "expiration", "initial-blocks", "max-accrued-blocks", "radius-list", + "radius-list"); + private static final List<String> ADMIN_OPTIONS = Arrays.asList( + "player-command", "player-deny-godmode", "player-deny-hunger", "player-gamemode", + "player-health-regen", "player-keep-inventory", "player-keep-level", "player-walk-speed", + "radius-inspect", "radius-list"); + + private final String id; + private final String name; + private final Class<T> allowed; + private Component description; + private Boolean isGlobal; + private Boolean isAdmin; + + GDOption(OptionBuilder<T> builder) { + this(builder.id, builder.name, builder.typeClass); + } + + public GDOption(String id, String name, Class<T> allowed) { + this.id = id; + this.name = name; + this.allowed = allowed; + this.isAdmin = ADMIN_OPTIONS.contains(name); + this.isGlobal = GLOBAL_OPTIONS.contains(name); + } + + @Override + public String getId() { + return this.id; + } + + public String getPermission() { + return "griefdefender." + this.name.toLowerCase(); + } + + public String getName() { + return this.name; + } + + public boolean isGlobal() { + return this.isGlobal; + } + + public boolean isAdmin() { + return this.isAdmin; + } + + @Override + public Class<T> getAllowedType() { + return this.allowed; + } + + @Override + public Component getDescription() { + if (this.description == null) { + this.description = this.createDescription(); + } + return this.description; + } + + @Override + public String toString() { + return this.name; + } + + private Component createDescription() { + final Component description = GriefDefenderPlugin.getInstance().messageData.getMessage("option-description-" + this.name.toLowerCase()); + if (description != null) { + return description; + } + return TextComponent.of("Not defined."); + } + + public void reloadDescription() { + this.description = null; + } + + @Override + public T getDefaultValue() { + if (this.allowed.isAssignableFrom(Tristate.class)) { + return (T) Tristate.UNDEFINED; + } + if (this.allowed.isAssignableFrom(String.class)) { + return (T) "undefined"; + } + if (this.allowed.isAssignableFrom(Integer.class)) { + return (T) Integer.valueOf(-1); + } + if (this.allowed.isAssignableFrom(Double.class)) { + return (T) Double.valueOf(0); + } + if (this.allowed.isAssignableFrom(Boolean.class)) { + return (T) Boolean.FALSE; + } + if (this.allowed.isAssignableFrom(CreateModeType.class)) { + return (T) CreateModeTypes.AREA; + } + if (this.allowed.isAssignableFrom(GameModeType.class)) { + return (T) GameModeTypes.UNDEFINED; + } + if (this.allowed.isAssignableFrom(WeatherType.class)) { + return (T) WeatherTypes.UNDEFINED; + } + return null; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof Option)) { + return false; + } + return this.id.equals(((Option<?>) o).getId()); + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + @Nullable + public boolean validateStringValue(String value, boolean log) { + if (value.equalsIgnoreCase("undefined")) { + return false; + } else if (this.allowed == Integer.class) { + try { + Integer.parseInt(value); + } catch (NumberFormatException e) { + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid Integer value '" + value + "', entered for option " + this.getName() + + ".\nYou must use a valid number. Skipping..."); + } + return false; + } + return true; + } else if (this.allowed == Boolean.class) { + if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { + return true; + } + + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid Boolean value '" + value + "', entered for option " + this.getName() + + ".\nAcceptable values are : true, or false. Skipping..."); + } + return false; + } else if (this.allowed == Double.class) { + try { + Double.parseDouble(value); + } catch (NumberFormatException e) { + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid Double value '" + value + "', entered for option " + this.getName() + + ".\nYou must use a valid number. Skipping..."); + } + return false; + } + return true; + } else if (this.allowed == Tristate.class) { + if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { + return true; + } + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid Tristate value '" + value + "', entered for option " + this.getName() + + ".\nAcceptable values are : true, false, or undefined. Skipping..."); + } + } else if (this.allowed == CreateModeType.class) { + if (value.equalsIgnoreCase("area") || value.equalsIgnoreCase("volume")) { + return true; + } + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid CreateModeType value '" + value + "', entered for option " + this.getName() + + ".\nAcceptable values are : area, volume, or undefined. Skipping..."); + } + } else if (this.allowed == GameModeType.class) { + if (value.equalsIgnoreCase("survival") || value.equalsIgnoreCase("adventure") || value.equalsIgnoreCase("creative") || value.equalsIgnoreCase("spectator")) { + return true; + } + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid GameModeType value '" + value + "', entered for option " + this.getName() + + ".\nAcceptable values are : adventure, creative, survival, spectator, or undefined. Skipping..."); + } + } else if (this.allowed == WeatherType.class) { + if (value.equalsIgnoreCase("clear") || value.equalsIgnoreCase("rain")) { + return true; + } + if (log) { + GriefDefenderPlugin.getInstance().getLogger().warn("Invalid WeatherType value '" + value + "', entered for option " + this.getName() + + ".\nAcceptable values are : clear, rain, or undefined. Skipping..."); + } + } + + return false; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/option/OptionBuilder.java b/sponge/src/main/java/com/griefdefender/permission/option/OptionBuilder.java new file mode 100644 index 0000000..1c5993a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/option/OptionBuilder.java @@ -0,0 +1,69 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.option; + +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.Option.Builder; +import com.griefdefender.registry.OptionRegistryModule; + +public final class OptionBuilder<T> implements Option.Builder<T> { + + Class<T> typeClass; + String id; + String name; + + @Override + public Builder<T> type(Class<T> tClass) { + this.typeClass = tClass; + return this; + } + + @Override + public Builder<T> id(String id) { + this.id = id; + return this; + } + + @Override + public Builder<T> name(String name) { + this.name = name; + return this; + } + + @Override + public Option<T> build() { + final GDOption<T> key = new GDOption<>(this); + OptionRegistryModule.getInstance().registerCustomType(key); + return key; + } + + @Override + public Builder<T> reset() { + this.typeClass = null; + this.id = null; + this.name = null; + return this; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/permission/option/type/GDCreateModeType.java b/sponge/src/main/java/com/griefdefender/permission/option/type/GDCreateModeType.java new file mode 100644 index 0000000..c73cf31 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/option/type/GDCreateModeType.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.option.type; + +import com.griefdefender.api.permission.option.type.CreateModeType; + +public class GDCreateModeType implements CreateModeType { + + private final String id; + private final String name; + + public GDCreateModeType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + public String toString() { + return this.name; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/option/type/GDGameModeType.java b/sponge/src/main/java/com/griefdefender/permission/option/type/GDGameModeType.java new file mode 100644 index 0000000..2c7d532 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/option/type/GDGameModeType.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.option.type; + +import com.griefdefender.api.permission.option.type.GameModeType; + +public class GDGameModeType implements GameModeType { + + private final String id; + private final String name; + + public GDGameModeType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + public String toString() { + return this.name; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/option/type/GDWeatherType.java b/sponge/src/main/java/com/griefdefender/permission/option/type/GDWeatherType.java new file mode 100644 index 0000000..4eb1ab8 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/option/type/GDWeatherType.java @@ -0,0 +1,52 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.option.type; + +import com.griefdefender.api.permission.option.type.WeatherType; + +public class GDWeatherType implements WeatherType { + + private final String id; + private final String name; + + public GDWeatherType(String id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getName() { + return this.name; + } + + public String toString() { + return this.name; + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ui/ClaimClickData.java b/sponge/src/main/java/com/griefdefender/permission/ui/ClaimClickData.java new file mode 100644 index 0000000..37edaa1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ui/ClaimClickData.java @@ -0,0 +1,37 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.ui; + +import com.griefdefender.claim.GDClaim; + +public class ClaimClickData { + public final GDClaim claim; + public final Object value; + + public ClaimClickData(GDClaim claim, Object value) { + this.claim = claim; + this.value = value; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/permission/ui/FlagData.java b/sponge/src/main/java/com/griefdefender/permission/ui/FlagData.java new file mode 100644 index 0000000..dd79915 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ui/FlagData.java @@ -0,0 +1,143 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; + +import net.kyori.text.format.TextColor; + +public class FlagData { + + public Flag flag; + public Map<Integer, FlagContextHolder> flagContextMap = new HashMap<>(); + + public FlagData(Flag flag, Boolean value, MenuType type, Set<Context> contexts) { + this.flag = flag; + this.addContexts(flag, value, type, contexts); + } + + public boolean addContexts(Flag flag, Boolean value, MenuType type, Set<Context> contexts) { + final Set<Context> filteredContexts = UIHelper.getFilteredContexts(contexts); + final int hashCode = Objects.hash(filteredContexts); + final FlagContextHolder flagHolder = this.flagContextMap.get(hashCode); + if (flagHolder != null) { + if (flagHolder.getType() == MenuType.CLAIM && type == MenuType.DEFAULT) { + // ignore + return false; + } + // Context Default Types have higher priority than global + if (contexts.contains(ClaimContexts.GLOBAL_DEFAULT_CONTEXT)) { + for (Context context : flagHolder.getAllContexts()) { + if (context.getKey().equalsIgnoreCase("gd_claim_default")) { + if (!context.getValue().equalsIgnoreCase("global")) { + return false; + } + } + } + } + // Context Override Types have higher priority than global + if (contexts.contains(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT)) { + for (Context context : flagHolder.getAllContexts()) { + if (context.getKey().equalsIgnoreCase("gd_claim_override")) { + if (!context.getValue().equalsIgnoreCase("global")) { + return false; + } + } + } + } + } + this.flagContextMap.put(Objects.hash(filteredContexts), new FlagContextHolder(flag, value, type, contexts)); + return true; + } + + public class FlagContextHolder { + private Set<Context> contexts; + private Flag flag; + private Boolean value; + private MenuType type; + private TextColor color; + private Set<Context> removedContexts = new HashSet<>(); + + public FlagContextHolder(Flag flag, Boolean value, MenuType type, Set<Context> contexts) { + this.flag = flag; + this.value = value; + this.contexts = this.getFilteredContexts(contexts); + this.type = type; + this.color = UIHelper.getPermissionMenuTypeColor(type); + } + + public Flag getFlag() { + return this.flag; + } + + public Boolean getValue() { + return this.value; + } + + public MenuType getType() { + return this.type; + } + + public TextColor getColor() { + return this.color; + } + + public Set<Context> getRemovedContexts() { + return this.removedContexts; + } + + public Set<Context> getContexts() { + return this.contexts; + } + + public Set<Context> getAllContexts() { + Set<Context> allContexts = new HashSet<>(); + allContexts.addAll(this.removedContexts); + allContexts.addAll(this.contexts); + return allContexts; + } + + private Set<Context> getFilteredContexts(Set<Context> contexts) { + Set<Context> filteredContexts = new HashSet<>(contexts); + for (Context context : contexts) { + if (context.getKey().contains("gd_claim") || context.getKey().equals("server")) { + this.removedContexts.add(context); + filteredContexts.remove(context); + } + } + + return filteredContexts; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ui/MenuType.java b/sponge/src/main/java/com/griefdefender/permission/ui/MenuType.java new file mode 100644 index 0000000..abae357 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ui/MenuType.java @@ -0,0 +1,35 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.ui; + +public enum MenuType { + + DEFAULT, + CLAIM, + OVERRIDE, + INHERIT, + GROUP, + PLAYER +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ui/OptionData.java b/sponge/src/main/java/com/griefdefender/permission/ui/OptionData.java new file mode 100644 index 0000000..45cd00e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ui/OptionData.java @@ -0,0 +1,122 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.option.Option; + +import net.kyori.text.format.TextColor; + +@SuppressWarnings("rawtypes") +public class OptionData { + + public Option option; + public Map<Integer, OptionContextHolder> optionContextMap = new HashMap<>(); + + public OptionData(Option option, String value, MenuType type, Set<Context> contexts) { + this.option = option; + this.addContexts(option, value, type, contexts); + } + + public boolean addContexts(Option option, String value, MenuType type, Set<Context> contexts) { + final Set<Context> filteredContexts = UIHelper.getFilteredContexts(contexts); + final int hashCode = Objects.hash(filteredContexts); + final OptionContextHolder flagHolder = this.optionContextMap.get(hashCode); + if (flagHolder != null) { + if (flagHolder.getType() == MenuType.CLAIM && type == MenuType.DEFAULT) { + // ignore + return false; + } + } + this.optionContextMap.put(Objects.hash(filteredContexts), new OptionContextHolder(option, value, type, contexts)); + return true; + } + + public class OptionContextHolder { + private Set<Context> contexts; + private Option option; + private String value; + private MenuType type; + private TextColor color; + private Set<Context> removedContexts = new HashSet<>(); + + public OptionContextHolder(Option option, String value, MenuType type, Set<Context> contexts) { + this.option = option; + this.value = value; + this.contexts = this.getFilteredContexts(contexts); + this.type = type; + this.color = UIHelper.getPermissionMenuTypeColor(type); + } + + public Option getOption() { + return this.option; + } + + public String getValue() { + return this.value; + } + + public MenuType getType() { + return this.type; + } + + public TextColor getColor() { + return this.color; + } + + public Set<Context> getRemovedContexts() { + return this.removedContexts; + } + + public Set<Context> getContexts() { + return this.contexts; + } + + public Set<Context> getAllContexts() { + Set<Context> allContexts = new HashSet<>(); + allContexts.addAll(this.removedContexts); + allContexts.addAll(this.contexts); + return allContexts; + } + + private Set<Context> getFilteredContexts(Set<Context> contexts) { + Set<Context> filteredContexts = new HashSet<>(contexts); + for (Context context : contexts) { + if (context.getKey().contains("gd_claim") || context.getKey().equals("server")) { + this.removedContexts.add(context); + filteredContexts.remove(context); + } + } + + return filteredContexts; + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/permission/ui/UIHelper.java b/sponge/src/main/java/com/griefdefender/permission/ui/UIHelper.java new file mode 100644 index 0000000..3b7ac66 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/permission/ui/UIHelper.java @@ -0,0 +1,187 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.permission.ui; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.ui.FlagData.FlagContextHolder; + +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import net.kyori.text.serializer.plain.PlainComponentSerializer; + +public class UIHelper { + + public static Comparator<Component> PLAIN_COMPARATOR = (text1, text2) -> PlainComponentSerializer.INSTANCE.serialize(text1).compareTo(PlainComponentSerializer.INSTANCE.serialize(text2)); + + public static List<Component> stripeText(List<Component> texts) { + Collections.sort(texts, PLAIN_COMPARATOR); + + ImmutableList.Builder<Component> finalTexts = ImmutableList.builder(); + for (int i = 0; i < texts.size(); i++) { + Component text = texts.get(i); + if (i % 2 == 0) { + text = text.color(TextColor.GREEN); + } else { + text = text.color(TextColor.AQUA); + } + + finalTexts.add(text); + } + return finalTexts.build(); + } + + public static Component getPermissionMenuTypeHoverText(FlagContextHolder flagHolder, MenuType menuType) { + if (flagHolder.getType() == MenuType.DEFAULT && menuType == MenuType.CLAIM) { + return TextComponent.builder() + .append(MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.FLAG_NOT_SET, + ImmutableMap.of("flag", TextComponent.of(flagHolder.getFlag().getName(), TextColor.GREEN), + "value", TextComponent.of(flagHolder.getValue(), TextColor.LIGHT_PURPLE)))).build(); + } + + return getPermissionMenuTypeHoverText(menuType); + } + + public static Component getPermissionMenuTypeHoverText(MenuType type) { + Component hoverText = TextComponent.empty(); + if (type == MenuType.DEFAULT) { + hoverText = TextComponent.builder("") + .append(MessageCache.getInstance().TITLE_DEFAULT.color(TextColor.LIGHT_PURPLE)) + .append(" : ") + .append(MessageCache.getInstance().FLAG_UI_INFO_DEFAULT) + .build(); + } else if (type == MenuType.CLAIM) { + hoverText = TextComponent.builder("") + .append(MessageCache.getInstance().TITLE_CLAIM.color(TextColor.GOLD)) + .append(" : ") + .append(MessageCache.getInstance().FLAG_UI_INFO_CLAIM) + .build(); + } else if (type == MenuType.OVERRIDE) { + hoverText = TextComponent.builder("") + .append(MessageCache.getInstance().TITLE_OVERRIDE.color(TextColor.RED)) + .append(" : ") + .append(MessageCache.getInstance().FLAG_UI_INFO_OVERRIDE) + .build(); + } else if (type == MenuType.INHERIT) { + hoverText = TextComponent.builder("") + .append(MessageCache.getInstance().TITLE_INHERIT.color(TextColor.AQUA)) + .append(" : ") + .append(MessageCache.getInstance().FLAG_UI_INFO_INHERIT) + .build(); + } + return hoverText; + } + + public static Component getBaseOptionOverlayText(String option) { + String baseFlag = option.replace(GDPermissions.OPTION_BASE + ".", ""); + int endIndex = baseFlag.indexOf("."); + if (endIndex != -1) { + baseFlag = baseFlag.substring(0, endIndex); + } + + final Option<?> flag = GriefDefender.getRegistry().getType(Option.class, baseFlag).orElse(null); + if (flag == null) { + return MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.OPTION_NOT_FOUND, ImmutableMap.of( + "option", baseFlag)); + } + + return flag.getDescription(); + } + + public static Component getFriendlyContextString(Claim claim, Set<Context> contexts) { + if (contexts.isEmpty()) { + return TextComponent.of("[]", TextColor.WHITE); + } + + TextComponent.Builder builder = TextComponent.builder(); + final Iterator<Context> iterator = contexts.iterator(); + while (iterator.hasNext()) { + final Context context = iterator.next(); + builder.append("\n[", TextColor.WHITE) + .append(context.getKey(), TextColor.GREEN) + .append("=", TextColor.GRAY) + .append(context.getValue(), TextColor.WHITE); + + if (iterator.hasNext()) { + builder.append("], "); + } else { + builder.append("]"); + } + } + return builder.build(); + } + + public static TextColor getPermissionMenuTypeColor(MenuType type) { + TextColor color = TextColor.LIGHT_PURPLE; + if (type == MenuType.DEFAULT) { + color = TextColor.LIGHT_PURPLE; + } else if (type == MenuType.CLAIM) { + color = TextColor.GOLD; + } else if (type == MenuType.INHERIT) { + color = TextColor.AQUA; + } else { + color = TextColor.RED; + } + + return color; + } + + public static boolean containsCustomContext(Set<Context> contexts) { + for (Context context : contexts) { + if (context.getKey().equals("gd_claim_default") || context.getKey().equals("server") || context.getKey().equals("gd_claim")) { + continue; + } + + return true; + } + return false; + } + + public static Set<Context> getFilteredContexts(Set<Context> contexts) { + Set<Context> filteredContexts = new HashSet<>(contexts); + for (Context context : contexts) { + if (context.getKey().contains("gd_claim") || context.getKey().equals("server")) { + filteredContexts.remove(context); + } + } + + return filteredContexts; + } +} diff --git a/sponge/src/main/java/com/griefdefender/provider/LuckPermsProvider.java b/sponge/src/main/java/com/griefdefender/provider/LuckPermsProvider.java new file mode 100644 index 0000000..e2d9b7e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/provider/LuckPermsProvider.java @@ -0,0 +1,885 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.provider; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableSet; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.ContextKeys; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.ResultTypes; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.listener.LuckPermsEventHandler; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionResult; +import com.griefdefender.permission.GDPermissionUser; +import me.lucko.luckperms.LuckPerms; +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.DataMutateResult; +import me.lucko.luckperms.api.Group; +import me.lucko.luckperms.api.LuckPermsApi; +import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.PermissionHolder; +import me.lucko.luckperms.api.User; +import me.lucko.luckperms.api.caching.MetaData; +import me.lucko.luckperms.api.caching.PermissionData; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.api.context.MutableContextSet; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.Map.Entry; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FilenameUtils; + +public class LuckPermsProvider implements PermissionProvider { + + private final Cache<String, Group> groupCache = Caffeine.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES) + .build(); + private final Cache<String, User> userCache = Caffeine.newBuilder().expireAfterAccess(30, TimeUnit.MINUTES) + .build(); + + public static Comparator<Set<Context>> CONTEXT_COMPARATOR = new Comparator<Set<Context>>() { + @Override + public int compare(Set<Context> s1, Set<Context> s2) { + if (s1.size() > s2.size()) { + return -1; + } + if (s1.size() < s2.size()) { + return 1; + } + return s1.equals(s2) ? 0 : -1; + } + }; + + private final LuckPermsApi luckPermsApi; + + public LuckPermsProvider() { + this.luckPermsApi = LuckPerms.getApi(); + //new LuckPermsEventHandler(this.luckPermsApi); + } + + public LuckPermsApi getApi() { + return this.luckPermsApi; + } + + @Override + public boolean hasGroupSubject(String identifier) { + return this.getGroupSubject(identifier) != null; + } + + public PermissionHolder getLuckPermsHolder(GDPermissionHolder holder) { + if (holder.getIdentifier().equalsIgnoreCase("default")) { + return this.luckPermsApi.getGroup("default"); + } + if (holder instanceof GDPermissionUser) { + return this.getLuckPermsUser(holder.getIdentifier()); + } + + return this.getLuckPermsGroup(holder.getIdentifier()); + } + + public User getLuckPermsUser(String identifier) { + User user = this.userCache.getIfPresent(identifier); + if (user != null) { + return user; + } + + UUID uuid = null; + if (identifier.length() == 36) { + try { + uuid = UUID.fromString(identifier); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + if (uuid != null) { + user = this.getUserSubject(uuid); + } + + if (user == null) { + user = this.luckPermsApi.getUser(identifier); + } + if (user != null) { + this.userCache.put(identifier, user); + } + + return user; + } + + public Group getLuckPermsGroup(String identifier) { + if (identifier.equalsIgnoreCase("default")) { + return this.luckPermsApi.getGroup("default"); + } + Group group = this.groupCache.getIfPresent(identifier); + if (group != null) { + return group; + } + + group = this.luckPermsApi.getGroup(identifier); + if (group != null) { + this.groupCache.put(identifier, group); + } + return group; + } + + public Group getGroupSubject(String identifier) { + final Group group = this.luckPermsApi.getGroupManager().getGroup(identifier); + if (group != null) { + return group; + } + + try { + return this.luckPermsApi.getGroupManager().loadGroup(identifier).get().orElse(null); + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + return null; + } + } + + /* public GDPermissionUser getUserSubject(String name) { + final User user = this.luckPermsApi.getUserManager().getUser(name); + if (user != null) { + return new GDPermissionUser(user); + } + + try { + final UUID uuid = this.luckPermsApi.getUserManager().lookupUuid(name).get(); + if (uuid != null) { + return this.luckPermsApi.getUserManager().loadUser(uuid).get(); + } + return null; + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + return null; + } + }*/ + + public UUID lookupUserUniqueId(String name) { + final User user = this.getLuckPermsUser(name); + if (user != null) { + return user.getUuid(); + } + return null; + } + + public User getUserSubject(UUID uuid) { + User user = this.luckPermsApi.getUserManager().getUser(uuid); + if (user != null) { + return user; + } + + try { + user = this.luckPermsApi.getUserManager().loadUser(uuid).get(); + if (user != null) { + return user; + } + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + return null; + } + return null; + } + + public List<String> getAllLoadedPlayerNames() { + List<String> subjectList = new ArrayList<>(); + for (User user : this.luckPermsApi.getUserManager().getLoadedUsers()) { + final String name = user.getName(); + if (name != null) { + subjectList.add(name); + } + } + if (!subjectList.contains("public")) { + subjectList.add("public"); + } + return subjectList; + } + + public List<String> getAllLoadedGroupNames() { + List<String> subjectList = new ArrayList<>(); + for (Group group : this.luckPermsApi.getGroupManager().getLoadedGroups()) { + final String name = group.getName(); + if (name != null) { + subjectList.add(name); + } + } + if (!subjectList.contains("public")) { + subjectList.add("public"); + } + return subjectList; + } + + public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder) { + addActiveContexts(contexts, permissionHolder, null, null); + } + + public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder, GDPlayerData playerData, Claim claim) { + if (playerData != null) { + playerData.ignoreActiveContexts = true; + } + final PermissionHolder luckPermsHolder = this.getLuckPermsHolder(permissionHolder); + if (luckPermsHolder instanceof Group) { + contexts.addAll(this.getGDContexts(this.luckPermsApi.getContextManager().getStaticContext().mutableCopy())); + return; + } + + ImmutableContextSet contextSet = this.luckPermsApi.getContextManager().lookupApplicableContext((User) luckPermsHolder).orElse(null); + if (contextSet == null) { + contextSet = this.luckPermsApi.getContextManager().getStaticContext(); + } + if (contextSet == null) { + return; + } + MutableContextSet activeContexts = contextSet.mutableCopy(); + if (playerData != null && claim != null) { + final Claim parent = claim.getParent().orElse(null); + if (parent != null && claim.getData() != null && claim.getData().doesInheritParent()) { + activeContexts.remove(parent.getContext().getKey(), parent.getContext().getValue()); + } else { + activeContexts.remove(claim.getContext().getKey(), claim.getContext().getValue()); + } + } + contexts.addAll(this.getGDContexts(activeContexts)); + } + + public void clearPermissions(GDClaim claim) { + Map<Set<Context>, Map<String, Boolean>> permissionMap = this.getPermanentPermissions(GriefDefenderPlugin.DEFAULT_HOLDER); + for (Entry<Set<Context>, Map<String, Boolean>> mapEntry : permissionMap.entrySet()) { + for (Context context : mapEntry.getKey()) { + if (context.getKey().equalsIgnoreCase("gd_claim") && context.getValue().equalsIgnoreCase(claim.getUniqueId().toString())) { + this.clearPermissions(GriefDefenderPlugin.DEFAULT_HOLDER, mapEntry.getKey()); + break; + } + } + } + } + + public void clearPermissions(GDPermissionHolder holder, Context context) { + clearPermissions(holder, ImmutableSet.of(context)); + } + + public void clearPermissions(GDPermissionHolder holder, Set<Context> contexts) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return; + } + + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + permissionHolder.clearNodes(set); + this.savePermissionHolder(permissionHolder); + } + + public boolean holderHasPermission(GDPermissionHolder holder, String permission) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return false; + } + + return permissionHolder.getCachedData().getPermissionData(Contexts.allowAll()).getPermissionValue(permission) == me.lucko.luckperms.api.Tristate.TRUE; + } + + public Map<String, Boolean> getPermissions(GDPermissionHolder holder, Set<Context> contexts) { + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + Contexts context = Contexts.global().setContexts(set); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + PermissionData cachedData = permissionHolder.getCachedData().getPermissionData(context); + return cachedData.getImmutableBacking(); + } + + public Map<String, String> getOptions(GDPermissionHolder holder, Set<Context> contexts) { + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + Contexts context = Contexts.global().setContexts(set); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + MetaData cachedData = permissionHolder.getCachedData().getMetaData(context); + return cachedData.getMeta(); + } + + public Map<Set<Context>, Map<String, Boolean>> getPermanentPermissions(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final ImmutableCollection<Node> nodes = permissionHolder.getNodes().values(); + Map<Set<Context>, Map<String, Boolean>> permanentPermissionMap = new TreeMap<Set<Context>, Map<String, Boolean>>(CONTEXT_COMPARATOR); + Map<ContextSet, Set<Context>> contextMap = new HashMap<>(); + for (Node node : nodes) { + if (node.isMeta()) { + continue; + } + + String serverName = node.getServer().orElse(null); + if (serverName != null && serverName.equalsIgnoreCase("global")) { + serverName = null; + } + Set<Context> contexts = null; + if (contextMap.get(node.getContexts()) == null) { + contexts = getGPContexts(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + contextMap.put(node.getContexts(), contexts); + } else { + contexts = contextMap.get(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + } + Map<String, Boolean> permissionEntry = permanentPermissionMap.get(contexts); + if (permissionEntry == null) { + permissionEntry = new HashMap<>(); + permissionEntry.put(node.getPermission(), node.getValue()); + permanentPermissionMap.put(contexts, permissionEntry); + } else { + permissionEntry.put(node.getPermission(), node.getValue()); + } + } + + return permanentPermissionMap; + } + + public Map<Set<Context>, Map<String, Boolean>> getTransientPermissions(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final Set<? extends Node> nodes = permissionHolder.getTransientPermissions(); + Map<Set<Context>, Map<String, Boolean>> transientPermissionMap = new TreeMap<Set<Context>, Map<String, Boolean>>(CONTEXT_COMPARATOR); + Map<ContextSet, Set<Context>> contextMap = new HashMap<>(); + for (Node node : nodes) { + if (node.isMeta()) { + continue; + } + + String serverName = node.getServer().orElse(null); + if (serverName != null && serverName.equalsIgnoreCase("global")) { + serverName = null; + } + Set<Context> contexts = null; + if (contextMap.get(node.getContexts()) == null) { + contexts = getGPContexts(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + contextMap.put(node.getContexts(), contexts); + } else { + contexts = contextMap.get(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + } + Map<String, Boolean> permissionEntry = transientPermissionMap.get(contexts); + if (permissionEntry == null) { + permissionEntry = new HashMap<>(); + permissionEntry.put(node.getPermission(), node.getValue()); + transientPermissionMap.put(contexts, permissionEntry); + } else { + permissionEntry.put(node.getPermission(), node.getValue()); + } + } + return transientPermissionMap; + } + + public Map<Set<Context>, Map<String, String>> getPermanentOptions(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final ImmutableCollection<Node> nodes = permissionHolder.getNodes().values(); + Map<Set<Context>, Map<String, String>> permanentPermissionMap = new TreeMap<Set<Context>, Map<String, String>>(CONTEXT_COMPARATOR); + Map<ContextSet, Set<Context>> contextMap = new HashMap<>(); + for (Node node : nodes) { + if (!node.isMeta()) { + continue; + } + String serverName = node.getServer().orElse(null); + if (serverName != null && serverName.equalsIgnoreCase("global")) { + serverName = null; + } + Set<Context> contexts = null; + if (contextMap.get(node.getContexts()) == null) { + contexts = getGPContexts(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + contextMap.put(node.getContexts(), contexts); + } else { + contexts = contextMap.get(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + } + Map<String, String> metaEntry = permanentPermissionMap.get(contexts); + if (metaEntry == null) { + metaEntry = new HashMap<>(); + metaEntry.put(node.getMeta().getKey(), node.getMeta().getValue()); + permanentPermissionMap.put(contexts, metaEntry); + } else { + metaEntry.put(node.getMeta().getKey(), node.getMeta().getValue()); + } + } + return permanentPermissionMap; + } + + public Map<Set<Context>, Map<String, String>> getTransientOptions(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final Set<? extends Node> nodes = permissionHolder.getTransientPermissions(); + Map<Set<Context>, Map<String, String>> permanentPermissionMap = new TreeMap<Set<Context>, Map<String, String>>(CONTEXT_COMPARATOR); + Map<ContextSet, Set<Context>> contextMap = new HashMap<>(); + for (Node node : nodes) { + if (!node.isMeta()) { + continue; + } + String serverName = node.getServer().orElse(null); + if (serverName != null && serverName.equalsIgnoreCase("global")) { + serverName = null; + } + Set<Context> contexts = null; + if (contextMap.get(node.getContexts()) == null) { + contexts = getGPContexts(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + contextMap.put(node.getContexts(), contexts); + } else { + contexts = contextMap.get(node.getContexts()); + if (serverName != null && !serverName.equalsIgnoreCase("undefined")) { + contexts.add(new Context("server", serverName)); + } + } + Map<String, String> metaEntry = permanentPermissionMap.get(contexts); + if (metaEntry == null) { + metaEntry = new HashMap<>(); + metaEntry.put(node.getMeta().getKey(), node.getMeta().getValue()); + permanentPermissionMap.put(contexts, metaEntry); + } else { + metaEntry.put(node.getMeta().getKey(), node.getMeta().getValue()); + } + } + return permanentPermissionMap; + } + + public Map<String, String> getPermanentOptions(GDPermissionHolder holder, Set<Context> contexts) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final Set<? extends Node> nodes = permissionHolder.getPermissions(); + final Map<String, String> options = new HashMap<>(); + for (Node node : nodes) { + if (!node.isMeta()) { + continue; + } + + if (contexts == null) { + options.put(node.getMeta().getKey(), node.getMeta().getValue()); + } else if (getGPContexts(node.getContexts()).containsAll(contexts)) { + options.put(node.getMeta().getKey(), node.getMeta().getValue()); + } + } + return options; + } + + public Map<String, String> getTransientOptions(GDPermissionHolder holder, Set<Context> contexts) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final Set<? extends Node> nodes = permissionHolder.getTransientPermissions(); + final Map<String, String> options = new HashMap<>(); + for (Node node : nodes) { + if (!node.isMeta()) { + continue; + } + + if (contexts == null) { + options.put(node.getMeta().getKey(), node.getMeta().getValue()); + } else if (getGPContexts(node.getContexts()).containsAll(contexts)) { + options.put(node.getMeta().getKey(), node.getMeta().getValue()); + } + } + return options; + } + + public Map<Set<Context>, Map<String, Boolean>> getAllPermissions(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new HashMap<>(); + } + + final Set<? extends Node> nodes = permissionHolder.getAllNodes(); + Map<Set<Context>, Map<String, Boolean>> permissionMap = new HashMap<>(); + Map<ContextSet, Set<Context>> contextMap = new HashMap<>(); + for (Node node : nodes) { + Set<Context> contexts = null; + if (contextMap.get(node.getContexts()) == null) { + contexts = getGPContexts(node.getContexts()); + contextMap.put(node.getContexts(), contexts); + } else { + contexts = contextMap.get(node.getContexts()); + } + Map<String, Boolean> permissionEntry = permissionMap.get(contexts); + if (permissionEntry == null) { + permissionEntry = new HashMap<>(); + permissionEntry.put(node.getPermission(), node.getValue()); + permissionMap.put(contexts, permissionEntry); + } else { + permissionEntry.put(node.getPermission(), node.getValue()); + } + } + return permissionMap; + } + + public Set<Context> getGPContexts(ContextSet contextSet) { + final Set<Context> gpContexts = new HashSet<>(); + for (Map.Entry<String, String> mapEntry : contextSet.toSet()) { + if (mapEntry.getKey().startsWith("gd_") || mapEntry.getKey().equals("used_item") + || mapEntry.getKey().equals("source") || mapEntry.getKey().equals("target") + || mapEntry.getKey().equals("world") || mapEntry.getKey().equals("server") + || mapEntry.getKey().equals("state")) { + if (contextSet.containsKey(ContextKeys.CLAIM) && mapEntry.getKey().equals("server")) { + continue; + } + + gpContexts.add(new Context(mapEntry.getKey(), mapEntry.getValue())); + } + } + return gpContexts; + } + + public Tristate getPermissionValue(GDPermissionHolder holder, String permission) { + ImmutableContextSet set = ImmutableContextSet.empty(); + return this.getPermissionValue(holder, permission, set); + } + + + public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, MutableContextSet contexts) { + return getPermissionValue(claim, holder, permission, this.getGDContexts(contexts)); + } + + public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts) { + return getPermissionValue(claim, holder, permission, contexts, true); + } + + public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, boolean checkTransient) { + final Set<Context> activeContexts = new HashSet<>(); + this.addActiveContexts(activeContexts, holder, null, claim); + contexts.addAll(activeContexts); + final int contextHash = Objects.hash(claim, holder, permission, contexts); + final Cache<Integer, Tristate> cache = PermissionHolderCache.getInstance().getOrCreatePermissionCache(holder); + Tristate result = cache.getIfPresent(contextHash); + if (result != null) { + return result; + } + // check persistent permissions first + Map<Set<Context>, Map<String, Boolean>> permanentPermissions = getPermanentPermissions(holder); + for (Entry<Set<Context>, Map<String, Boolean>> entry : permanentPermissions.entrySet()) { + if (entry.getKey().isEmpty()) { + continue; + } + boolean match = true; + for (Context context : entry.getKey()) { + if (!contexts.contains(context)) { + match = false; + break; + } + } + if (match) { + for (Map.Entry<String, Boolean> permEntry : entry.getValue().entrySet()) { + if (FilenameUtils.wildcardMatch(permission, permEntry.getKey())) { + final Tristate value = Tristate.fromBoolean(permEntry.getValue()); + cache.put(contextHash, value); + return value; + } + } + // If we get here, continue on normally + continue; + } + } + + if (!checkTransient) { + return Tristate.UNDEFINED; + } + + // check transient permissions last + Map<Set<Context>, Map<String, Boolean>> transientPermissions = getTransientPermissions(holder); + for (Entry<Set<Context>, Map<String, Boolean>> entry : transientPermissions.entrySet()) { + if (entry.getKey().isEmpty()) { + continue; + } + boolean match = true; + for (Context context : entry.getKey()) { + if (!contexts.contains(context)) { + match = false; + break; + } + } + if (match) { + for (Map.Entry<String, Boolean> permEntry : entry.getValue().entrySet()) { + if (FilenameUtils.wildcardMatch(permission, permEntry.getKey())) { + final Tristate value = Tristate.fromBoolean(permEntry.getValue()); + cache.put(contextHash, value); + return value; + } + } + // If we get here, continue on normally + continue; + } + } + + cache.put(contextHash, Tristate.UNDEFINED); + return Tristate.UNDEFINED; + } + + public Tristate getPermissionValueWithRequiredContexts(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, String contextFilter) { + Map<Set<Context>, Map<String, Boolean>> permanentPermissions = getPermanentPermissions(holder); + for (Entry<Set<Context>, Map<String, Boolean>> entry : permanentPermissions.entrySet()) { + if (entry.getKey().isEmpty()) { + continue; + } + boolean match = true; + for (Context context : entry.getKey()) { + if (!contexts.contains(context)) { + match = false; + break; + } + } + + // Check for required contexts + for (Context context : contexts) { + if (!context.getKey().contains(contextFilter)) { + if (!entry.getKey().contains(context)) { + match = false; + break; + } + } + } + if (match) { + for (Map.Entry<String, Boolean> permEntry : entry.getValue().entrySet()) { + if (FilenameUtils.wildcardMatch(permission, permEntry.getKey())) { + final Tristate value = Tristate.fromBoolean(permEntry.getValue()); + return value; + } + } + } + } + return Tristate.UNDEFINED; + } + + public Tristate getPermissionValue(GDPermissionHolder holder, String permission, Set<Context> contexts) { + ImmutableContextSet contextSet = ImmutableContextSet.fromEntries(contexts); + return this.getPermissionValue(holder, permission, contextSet); + } + + public Tristate getPermissionValue(GDPermissionHolder holder, String permission, ContextSet contexts) { + Contexts context = Contexts.global().setContexts(contexts); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return Tristate.UNDEFINED; + } + + PermissionData cachedData = permissionHolder.getCachedData().getPermissionData(context); + return getGDTristate(cachedData.getPermissionValue(permission)); + } + + // To set options, pass "meta.option". + @Override + public String getOptionValue(GDPermissionHolder holder, Option option, Set<Context> contexts) { + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + Contexts context = Contexts.global().setContexts(set); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return null; + } + + MetaData metaData = permissionHolder.getCachedData().getMetaData(context); + return metaData.getMeta().get(option.getPermission()); + } + + public PermissionResult setOptionValue(GDPermissionHolder holder, String permission, String value, Set<Context> contexts) { + DataMutateResult result = null; + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + Contexts context = Contexts.global().setContexts(set); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return new GDPermissionResult(ResultTypes.FAILURE); + } + + MetaData metaData = permissionHolder.getCachedData().getMetaData(context); + for (Map.Entry<String, String> mapEntry : metaData.getMeta().entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(permission)) { + // Always unset existing meta first + final Node node = this.luckPermsApi.getNodeFactory().makeMetaNode(permission, mapEntry.getValue()).withExtraContext(set).build(); + result = permissionHolder.unsetPermission(node); + } + } + + final Node node = this.luckPermsApi.getNodeFactory().makeMetaNode(permission, value).withExtraContext(set).build(); + if (!value.equalsIgnoreCase("undefined")) { + result = permissionHolder.setPermission(node); + } + if (result != null && result.wasSuccess()) { + this.savePermissionHolder(permissionHolder); + return new GDPermissionResult(ResultTypes.SUCCESS); + } + return new GDPermissionResult(ResultTypes.FAILURE); + } + + public PermissionResult setPermissionValue(GDPermissionHolder holder, Flag flag, Tristate value, Set<Context> contexts) { + final boolean result = setPermissionValue(holder, flag.getPermission(), value, contexts); + if (result) { + return new GDPermissionResult(ResultTypes.SUCCESS); + } + return new GDPermissionResult(ResultTypes.FAILURE); + } + + public boolean setPermissionValue(GDPermissionHolder holder, String permission, Tristate value, Set<Context> contexts) { + DataMutateResult result = null; + ImmutableContextSet set = ImmutableContextSet.fromEntries(contexts); + final Node node = this.luckPermsApi.getNodeFactory().newBuilder(permission).setValue(value.asBoolean()).withExtraContext(set).build(); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return false; + } + + if (value == Tristate.UNDEFINED) { + result = permissionHolder.unsetPermission(node); + } else { + result = permissionHolder.setPermission(node); + } + + if (result.wasSuccess()) { + if (permissionHolder instanceof Group) { + final Group group = (Group) permissionHolder; + group.refreshCachedData(); + for (User user :this.luckPermsApi.getUserManager().getLoadedUsers()) { + user.refreshCachedData(); + } + // If a group is changed, we invalidate all cache + PermissionHolderCache.getInstance().invalidateAllPermissionCache(); + } else { + // We need to invalidate cache outside of LP listener so we can guarantee proper result returns + PermissionHolderCache.getInstance().getOrCreatePermissionCache(holder).invalidateAll(); + } + + this.savePermissionHolder(permissionHolder); + } + return result.wasSuccess(); + } + + public void setTransientOption(GDPermissionHolder holder, String permission, String value, Set<Context> contexts) { + MutableContextSet contextSet = MutableContextSet.fromEntries(contexts); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return; + } + + final Node node = this.luckPermsApi.getNodeFactory().makeMetaNode(permission, value).withExtraContext(contextSet).build(); + permissionHolder.setTransientPermission(node); + } + + public void setTransientPermission(GDPermissionHolder holder, String permission, Boolean value, Set<Context> contexts) { + MutableContextSet contextSet = MutableContextSet.fromEntries(contexts); + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return; + } + + final Node node = this.luckPermsApi.getNodeFactory().newBuilder(permission).setValue(value).withExtraContext(contextSet).build(); + permissionHolder.setTransientPermission(node); + } + + public void savePermissionHolder(PermissionHolder holder) { + if (holder instanceof User) { + this.luckPermsApi.getUserManager().saveUser((User) holder); + } else { + this.luckPermsApi.getGroupManager().saveGroup((Group) holder); + } + } + + public void refreshCachedData(GDPermissionHolder holder) { + final PermissionHolder permissionHolder = this.getLuckPermsHolder(holder); + if (permissionHolder == null) { + return; + } + permissionHolder.refreshCachedData(); + } + + public Set<Context> getGDContexts(ContextSet contextSet) { + final Set<Context> gdContexts = new HashSet<>(); + contextSet.forEach(entry -> { + gdContexts.add(new Context(entry.getKey(), entry.getValue())); + }); + + return gdContexts; + } + + public Tristate getGDTristate(me.lucko.luckperms.api.Tristate state) { + if (state == me.lucko.luckperms.api.Tristate.TRUE) { + return Tristate.TRUE; + } + if (state == me.lucko.luckperms.api.Tristate.FALSE) { + return Tristate.FALSE; + } + return Tristate.UNDEFINED; + } +} diff --git a/sponge/src/main/java/com/griefdefender/provider/MCClansProvider.java b/sponge/src/main/java/com/griefdefender/provider/MCClansProvider.java new file mode 100644 index 0000000..88db648 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/provider/MCClansProvider.java @@ -0,0 +1,41 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.provider; + +import nl.riebie.mcclans.api.ClanService; +import org.spongepowered.api.Sponge; + +public class MCClansProvider { + + private final ClanService clanService; + + public MCClansProvider() { + this.clanService = Sponge.getServiceManager().provide(ClanService.class).orElse(null); + } + + public ClanService getClanService() { + return this.clanService; + } +} diff --git a/sponge/src/main/java/com/griefdefender/provider/NucleusProvider.java b/sponge/src/main/java/com/griefdefender/provider/NucleusProvider.java new file mode 100644 index 0000000..f53740f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/provider/NucleusProvider.java @@ -0,0 +1,84 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.provider; + +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.storage.BaseStorage; +import com.griefdefender.util.SpongeUtil; +import io.github.nucleuspowered.nucleus.api.NucleusAPI; +import io.github.nucleuspowered.nucleus.api.exceptions.PluginAlreadyRegisteredException; +import io.github.nucleuspowered.nucleus.api.service.NucleusMessageTokenService; +import io.github.nucleuspowered.nucleus.api.service.NucleusPrivateMessagingService; +import net.kyori.text.Component; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.plugin.PluginContainer; + +import java.util.Optional; + +public class NucleusProvider { + + + public NucleusProvider() { + } + + public static Optional<NucleusPrivateMessagingService> getPrivateMessagingService() { + return NucleusAPI.getPrivateMessagingService(); + } + + public void registerTokens() { + NucleusMessageTokenService messageTokenService = NucleusAPI.getMessageTokenService(); + PluginContainer pc = GDBootstrap.getInstance().pluginContainer; + final BaseStorage dataStore = GriefDefenderPlugin.getInstance().dataStore; + try { + messageTokenService.register(GDBootstrap.getInstance().pluginContainer, + (tokenInput, commandSource, variables) -> { + // Each token will require something like this. + + // This token, town, will give the name of the town the player is currently in. + // Will be registered in Nucleus as "{{pl:griefdefender:town}}", with the shortened version of "{{town}}" + // This will return the name of the town the player is currently in. + if (tokenInput.equalsIgnoreCase("town") && commandSource instanceof Player) { + Player player = (Player) commandSource; + final GDPlayerData data = dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + + // Shamelessly stolen from PlayerEventHandler + if (data.inTown) { + final Component component = dataStore.getClaimAtPlayer(data, player.getLocation()).getTownClaim().getTownData().getTownTag().orElse(null); + return Optional.ofNullable(SpongeUtil.getSpongeText(component)); + } + } + + return Optional.empty(); + }); + } catch (PluginAlreadyRegisteredException ignored) { + // already been done. + } + + // register {{town}} from {{pl:griefdefender:town}} + messageTokenService.registerPrimaryToken("town", pc, "town"); + } +} diff --git a/sponge/src/main/java/com/griefdefender/provider/PermissionProvider.java b/sponge/src/main/java/com/griefdefender/provider/PermissionProvider.java new file mode 100644 index 0000000..abdb767 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/provider/PermissionProvider.java @@ -0,0 +1,337 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.provider; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissionHolder; + +/** + * Represents a provider of permission data. + * + * <p>This is the interface that a permissions plugin must implement and + * to provide permissions lookups for GriefDefender.</p> + */ +public interface PermissionProvider { + + /** + * Checks if the group identifier exists. + * + * @param identifier The group identifier + * @return whether the group exists + */ + boolean hasGroupSubject(String identifier); + + /** + * Performs a lookup for a UUID with matching + * username. + * + * @param name The username to search with + * @return The user uuid if available + */ + @Nullable UUID lookupUserUniqueId(String name); + + /** + * Retrieves an immutable list of all loaded player + * names that exist in permissions. + * + * @return An immutable list of player names or empty if none. + */ + List<String> getAllLoadedPlayerNames(); + + /** + * Retrieves an immutable list of all loaded group + * names that exist in permissions. + * + * @return An immutable list of group names or empty if none. + */ + List<String> getAllLoadedGroupNames(); + + /** + * Appends all active contexts to passed context set currently + * active on permission holder. + * + * @param contexts The set to append to + * @param permissionHolder The holder to check active contexts + */ + void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder); + + /** + * Appends all active contexts to passed context set currently + * active on permission holder. + * + * @param contexts The set to append to + * @param permissionHolder The holder to check active contexts + * @param playerData The player data + * @param claim The claim + */ + void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder, GDPlayerData playerData, Claim claim); + + /** + * Clears all permissions that contain {@link Claim#getContext()} + * from passed {@link Claim}. + * + * @param claim The claim + */ + void clearPermissions(GDClaim claim); + + /** + * Clears all permissions that contain {@link Context} + * from passed holder. + * + * @param claim The claim + */ + void clearPermissions(GDPermissionHolder holder, Context context); + + /** + * Clears all permissions that contain {@link Context}'s + * from passed player. + * + * @param claim The claim + */ + void clearPermissions(GDPermissionHolder holder, Set<Context> contexts); + + /** + * Checks if holder has permission. + * + * @param permission The permission + * @return whether the holder has permission + */ + boolean holderHasPermission(GDPermissionHolder holder, String permission); + + /** + * Gets all cached permissions of holder that contain specific {@link Context}'s. + * + * @param holder The holder + * @param contexts The contexts required + * @return An immutable map of cached permissions or empty if none. + */ + Map<String, Boolean> getPermissions(GDPermissionHolder holder, Set<Context> contexts); + + /** + * Gets all cached options of holder that contain specific {@link Context}'s. + * + * @param holder The holder + * @param contexts The contexts required + * @return An immutable map of cached options or empty if none. + */ + Map<String, String> getOptions(GDPermissionHolder holder, Set<Context> contexts); + + /** + * Gets all persisted permissions with associated contexts of holder. + * + * @param holder The holder + * @return An immutable map of persisted permissions or empty if none. + */ + Map<Set<Context>, Map<String, Boolean>> getPermanentPermissions(GDPermissionHolder holder); + + /** + * Gets all transient permissions with associated contexts of holder. + * + * @param holder The holder + * @return An immutable map of transient permissions or empty if none. + */ + Map<Set<Context>, Map<String, Boolean>> getTransientPermissions(GDPermissionHolder holder); + + /** + * Gets all persisted options with associated contexts of holder. + * + * @param holder The holder + * @return An immutable map of persisted options or empty if none. + */ + Map<Set<Context>, Map<String, String>> getPermanentOptions(GDPermissionHolder holder); + + /** + * Gets all transient options with associated contexts of holder. + * + * @param holder The holder + * @return An immutable map of transient options or empty if none. + */ + Map<Set<Context>, Map<String, String>> getTransientOptions(GDPermissionHolder holder); + + /** + * Gets all persisted options and associated values of holder. + * + * @param holder The holder + * @return An immutable map of persisted options or empty if none. + */ + Map<String, String> getPermanentOptions(GDPermissionHolder holder, Set<Context> contexts); + + /** + * Gets all transient options and associated values of holder. + * + * @param holder The holder + * @return An immutable map of transient options or empty if none. + */ + Map<String, String> getTransientOptions(GDPermissionHolder holder, Set<Context> contexts); + + /** + * Gets all persisted permissions, including inherited nodes, with associated contexts of holder. + * + * @param holder The holder + * @return An immutable map of persisted permissions or empty if none. + */ + Map<Set<Context>, Map<String, Boolean>> getAllPermissions(GDPermissionHolder holder); + + /** + * Gets the current value of a permission assigned to a holder. + * + * @param holder The holder + * @param permission The permission to check + * @return The permission value + */ + Tristate getPermissionValue(GDPermissionHolder holder, String permission); + + /** + * Gets the current value of a permission assigned to a holder. + * + * @param claim The current claim + * @param holder The holder + * @param permission The permission to check + * @param contexts The contexts + * @return The permission value + */ + Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts); + + /** + * Gets the current value of a permission assigned to a holder. + * + * @param claim The current claim + * @param holder The holder + * @param permission The permission to check + * @param contexts The contexts + * @param checkTransient Whether to check transient permissions + * @return The permission value + */ + Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, boolean checkTransient); + + /** + * Gets the current value of a permission assigned to a holder. + * + * @param holder The holder + * @param permission The permission to check + * @param contexts The contexts + * @return The permission value + */ + Tristate getPermissionValue(GDPermissionHolder holder, String permission, Set<Context> contexts); + + /** + * Gets the current value of a permission that contains all passed contexts + * assigned to a holder. + * + * @param claim The current claim + * @param holder The holder + * @param permission The permission to check + * @param contexts The contexts required + * @param contextFilter The context key to ignore for required contexts + * @return The permission value + */ + Tristate getPermissionValueWithRequiredContexts(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, String contextFilter); + + /** + * Gets the current value of an option assigned to a holder. + * + * @param holder The holder + * @param permission The permission to check + * @param contexts The contexts + * @return The option value + */ + String getOptionValue(GDPermissionHolder holder, Option option, Set<Context> contexts); + + /** + * Sets an option and value with contexts to a holder. + * + * @param holder The holder + * @param permission The permission + * @param value The value + * @param contexts The contexts + * @return The permission result + */ + PermissionResult setOptionValue(GDPermissionHolder holder, String permission, String value, Set<Context> contexts); + + /** + * Sets a permission and value with contexts to a holder. + * + * @param holder The holder + * @param flag The flag to use for permission + * @param value The value + * @param contexts The contexts + * @return The permission result + */ + PermissionResult setPermissionValue(GDPermissionHolder holder, Flag flag, Tristate value, Set<Context> contexts); + + /** + * Sets a permission and value with contexts to a holder. + * + * @param holder The holder + * @param permission The permission + * @param value The value + * @param contexts The contexts + * @return Whether the set permission operation was successful + */ + boolean setPermissionValue(GDPermissionHolder holder, String permission, Tristate value, Set<Context> contexts); + + /** + * Sets a transient option and value with contexts to a holder. + * + * @param holder The holder + * @param permission The permission + * @param value The value + * @param contexts The contexts + * @return Whether the set permission operation was successful + */ + void setTransientOption(GDPermissionHolder holder, String permission, String value, Set<Context> contexts); + + /** + * Sets a transient permission and value with contexts to a holder. + * + * @param holder The holder + * @param permission The permission + * @param value The value + * @param contexts The contexts + * @return Whether the set permission operation was successful + */ + void setTransientPermission(GDPermissionHolder holder, String permission, Boolean value, Set<Context> contexts); + + /** + * Refreshes all cached permission data of holder. + * + * @param holder The holder + */ + void refreshCachedData(GDPermissionHolder holder); +} diff --git a/sponge/src/main/java/com/griefdefender/registry/ChatTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/ChatTypeRegistryModule.java new file mode 100644 index 0000000..b52f082 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/ChatTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.GDChatType; +import com.griefdefender.api.ChatType; +import com.griefdefender.api.ChatTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class ChatTypeRegistryModule implements CatalogRegistryModule<ChatType> { + + private static ChatTypeRegistryModule instance; + + public static ChatTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, ChatType> registryMap = new HashMap<>(); + + @Override + public Optional<ChatType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<ChatType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(ChatTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final ChatType type = new GDChatType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(ChatType type) { + this.registryMap.put(type.getId().toLowerCase(Locale.ENGLISH), type); + } + + static { + instance = new ChatTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/ClaimTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/ClaimTypeRegistryModule.java new file mode 100644 index 0000000..899754b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/ClaimTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.claim.GDClaimType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class ClaimTypeRegistryModule implements CatalogRegistryModule<ClaimType> { + + private static ClaimTypeRegistryModule instance; + + public static ClaimTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, ClaimType> registryMap = new HashMap<>(); + + @Override + public Optional<ClaimType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<ClaimType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(ClaimTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final ClaimType type = new GDClaimType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(ClaimType type) { + this.registryMap.put(type.getId().toLowerCase(Locale.ENGLISH), type); + } + + static { + instance = new ClaimTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/CreateModeTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/CreateModeTypeRegistryModule.java new file mode 100644 index 0000000..83a338c --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/CreateModeTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.option.type.GDCreateModeType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class CreateModeTypeRegistryModule implements CatalogRegistryModule<CreateModeType> { + + private static CreateModeTypeRegistryModule instance; + + public static CreateModeTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, CreateModeType> registryMap = new HashMap<>(); + + @Override + public Optional<CreateModeType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<CreateModeType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(CreateModeTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final CreateModeType type = new GDCreateModeType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(CreateModeType type) { + // TODO Auto-generated method stub + + } + + static { + instance = new CreateModeTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/FlagRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/FlagRegistryModule.java new file mode 100644 index 0000000..372b218 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/FlagRegistryModule.java @@ -0,0 +1,92 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.flag.GDFlag; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class FlagRegistryModule implements CatalogRegistryModule<Flag> { + + private static FlagRegistryModule instance; + + public static FlagRegistryModule getInstance() { + return instance; + } + + private final Map<String, Flag> registryMap = new HashMap<>(); + + @Override + public Optional<Flag> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + id = id.replace("griefdefender.flag.", ""); + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<Flag> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(Flags.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final Flag flag = new GDFlag(id, name); + this.registryMap.put(id, flag); + return flag; + }); + } + + @Override + public void registerCustomType(Flag type) { + this.registryMap.put(type.getId().toLowerCase(Locale.ENGLISH), type); + GriefDefenderPlugin.getGlobalConfig().getConfig().permissionCategory.refreshFlags(); + GriefDefenderPlugin.getInstance().dataStore.setDefaultGlobalPermissions(); + } + + static { + instance = new FlagRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/GDRegistry.java b/sponge/src/main/java/com/griefdefender/registry/GDRegistry.java new file mode 100644 index 0000000..8aee539 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/GDRegistry.java @@ -0,0 +1,116 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.inject.Singleton; +import com.griefdefender.api.CatalogType; +import com.griefdefender.api.Registry; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ShovelType; +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.permission.ResultType; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.registry.CatalogRegistryModule; + +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +@Singleton +public class GDRegistry implements Registry { + + protected final static Map<Class<? extends CatalogType>, CatalogRegistryModule<?>> catalogRegistryMap = new IdentityHashMap<>(); + private static final Map<Class<?>, Supplier<?>> BUILDER_SUPPLIERS = new IdentityHashMap<>(); + + static { + catalogRegistryMap.put(ClaimType.class, (CatalogRegistryModule<ClaimType>) ClaimTypeRegistryModule.getInstance()); + catalogRegistryMap.put(Flag.class, (CatalogRegistryModule<Flag>) FlagRegistryModule.getInstance()); + catalogRegistryMap.put(ResultType.class, (CatalogRegistryModule<ResultType>) ResultTypeRegistryModule.getInstance()); + catalogRegistryMap.put(Option.class, (CatalogRegistryModule<Option>) OptionRegistryModule.getInstance()); + catalogRegistryMap.put(ShovelType.class, (CatalogRegistryModule<ShovelType>)ShovelTypeRegistryModule.getInstance()); + catalogRegistryMap.put(TrustType.class, (CatalogRegistryModule<TrustType>)TrustTypeRegistryModule.getInstance()); + } + + @Override + public <T> Registry registerBuilderSupplier(Class<T> builderClass, Supplier<? extends T> supplier) { + checkArgument(!BUILDER_SUPPLIERS.containsKey(builderClass), "Already registered a builder supplier!"); + BUILDER_SUPPLIERS.put(builderClass, supplier); + return this; + } + + @Override + public <T> T createBuilder(Class<T> builderClass) throws IllegalArgumentException { + final Supplier<?> supplier = BUILDER_SUPPLIERS.get(builderClass); + checkArgument(supplier != null, "Could not find a Supplier for the provided builder class: " + builderClass.getCanonicalName()); + return (T) supplier.get(); + } + + @Override + public <T extends CatalogType> Optional<T> getType(Class<T> typeClass, String id) { + CatalogRegistryModule<T> registryModule = getRegistryModuleFor(typeClass).orElse(null); + if (registryModule == null) { + return Optional.empty(); + } + return registryModule.getById(id.toLowerCase(Locale.ENGLISH)); + } + + @Override + public <T extends CatalogType> Collection<T> getAllOf(Class<T> typeClass) { + CatalogRegistryModule<T> registryModule = getRegistryModuleFor(typeClass).orElse(null); + if (registryModule == null) { + return Collections.emptyList(); + } + return registryModule.getAll(); + } + + @Override + public <T extends CatalogType> Collection<T> getAllFor(String pluginId, Class<T> typeClass) { + final CatalogRegistryModule<T> registryModule = getRegistryModuleFor(typeClass).orElse(null); + if (registryModule == null) { + return Collections.emptyList(); + } + ImmutableList.Builder<T> builder = ImmutableList.builder(); + registryModule.getAll() + .stream() + .filter(type -> pluginId.equals(type.getId().split(":")[0])) + .forEach(builder::add); + + return builder.build(); + } + + @SuppressWarnings("unchecked") + @Override + public <T extends CatalogType> Optional<CatalogRegistryModule<T>> getRegistryModuleFor(Class<T> catalogClass) { + return Optional.ofNullable((CatalogRegistryModule<T>) catalogRegistryMap.get(catalogClass)); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/GameModeTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/GameModeTypeRegistryModule.java new file mode 100644 index 0000000..76327c3 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/GameModeTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.GameModeTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.option.type.GDGameModeType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class GameModeTypeRegistryModule implements CatalogRegistryModule<GameModeType> { + + private static GameModeTypeRegistryModule instance; + + public static GameModeTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, GameModeType> registryMap = new HashMap<>(); + + @Override + public Optional<GameModeType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<GameModeType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(GameModeTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final GameModeType type = new GDGameModeType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(GameModeType type) { + // TODO Auto-generated method stub + + } + + static { + instance = new GameModeTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/OptionRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/OptionRegistryModule.java new file mode 100644 index 0000000..12257d4 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/OptionRegistryModule.java @@ -0,0 +1,141 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.Tristate; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.api.permission.option.type.CreateModeType; +import com.griefdefender.api.permission.option.type.GameModeType; +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.option.GDOption; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +@SuppressWarnings("rawtypes") +public class OptionRegistryModule implements CatalogRegistryModule<Option> { + + private static OptionRegistryModule instance; + + public static OptionRegistryModule getInstance() { + return instance; + } + + static { + instance = new OptionRegistryModule(); + } + + protected final Map<String, Option> registryMap = new HashMap<>(); + protected final Map<String, Option> customMap = new HashMap<>(); + + @Override + public Optional<Option> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<Option> getAll() { + return this.registryMap.values(); + } + + public Map<String, Option> getCustomAdditions() { + return this.customMap; + } + + @Override + public void registerDefaults() { + this.createKey("griefdefender:abandon-delay", "abandon-delay", Integer.class); + this.createKey("griefdefender:abandon-return-ratio", "abandon-return-ratio", Double.class); + this.createKey("griefdefender:create-limit", "create-limit", Integer.class); + this.createKey("griefdefender:expiration", "expiration", Integer.class); + this.createKey("griefdefender:max-size-x", "max-size-x", Integer.class); + this.createKey("griefdefender:max-size-y", "max-size-y", Integer.class); + this.createKey("griefdefender:max-size-z", "max-size-z", Integer.class); + this.createKey("griefdefender:min-size-x", "min-size-x", Integer.class); + this.createKey("griefdefender:min-size-y", "min-size-y", Integer.class); + this.createKey("griefdefender:min-size-z", "min-size-z", Integer.class); + this.createKey("griefdefender:tax-expiration", "tax-expiration", Integer.class); + this.createKey("griefdefender:tax-expiration-days-keep", "tax-expiration-days-keep", Integer.class); + this.createKey("griefdefender:tax-rate", "tax-rate", Double.class); + this.createKey("griefdefender:blocks-accrued-per-hour", "blocks-accrued-per-hour", Integer.class); + this.createKey("griefdefender:chest-expiration", "chest-expiration", Integer.class); + this.createKey("griefdefender:create-mode", "create-mode", CreateModeType.class); + this.createKey("griefdefender:economy-block-cost", "economy-block-cost", Double.class); + this.createKey("griefdefender:economy-block-sell-return", "economy-block-sell-return", Double.class); + this.createKey("griefdefender:initial-blocks", "initial-blocks", Integer.class); + this.createKey("griefdefender:max-accrued-blocks", "max-accrued-blocks", Integer.class); + this.createKey("griefdefender:max-level", "max-level", Integer.class); + this.createKey("griefdefender:min-level", "min-level", Integer.class); + this.createKey("griefdefender:radius-list", "radius-list", Integer.class); + this.createKey("griefdefender:radius-inspect", "radius-inspect", Integer.class); + this.createKey("griefdefender:raid", "raid", Boolean.class); + this.createKey("griefdefender:spawn-limit", "spawn-limit", Integer.class); + this.createKey("griefdefender:player-command", "player-command", String.class); + this.createKey("griefdefender:player-deny-flight", "player-deny-flight", Boolean.class); + this.createKey("griefdefender:player-deny-godmode", "player-deny-godmode", Boolean.class); + this.createKey("griefdefender:player-deny-hunger", "player-deny-hunger", Boolean.class); + this.createKey("griefdefender:player-gamemode", "player-gamemode", GameModeType.class); + this.createKey("griefdefender:player-health-regen", "player-health-regen", Double.class); + this.createKey("griefdefender:player-keep-inventory", "player-keep-inventory", Tristate.class); + this.createKey("griefdefender:player-keep-level", "player-keep-level", Tristate.class); + this.createKey("griefdefender:player-teleport-delay", "player-teleport-delay", Integer.class); + this.createKey("griefdefender:player-walk-speed", "player-walk-speed", Integer.class); + this.createKey("griefdefender:player-weather", "player-weather", WeatherType.class); + this.createKey("griefdefender:pvp", "pvp", Tristate.class); + + RegistryHelper.mapFields(Options.class, input -> { + final String name = input.replace("_", "-"); + return this.registryMap.get("griefdefender:" + name.toLowerCase(Locale.ENGLISH)); + }); + } + + private void createKey(String id, String name, Class<?> clazz) { + this.registryMap.put(id, new GDOption<>(id, name, clazz)); + } + + @Override + public void registerCustomType(Option type) { + this.customMap.put(type.getId(), type); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/ResultTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/ResultTypeRegistryModule.java new file mode 100644 index 0000000..421daf6 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/ResultTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.permission.ResultType; +import com.griefdefender.api.permission.ResultTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.GDResultType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class ResultTypeRegistryModule implements CatalogRegistryModule<ResultType> { + + private static ResultTypeRegistryModule instance; + + public static ResultTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, ResultType> registryMap = new HashMap<>(); + + @Override + public Optional<ResultType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<ResultType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(ResultTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final ResultType type = new GDResultType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(ResultType type) { + this.registryMap.put(type.getId().toLowerCase(Locale.ENGLISH), type); + } + + static { + instance = new ResultTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/ShovelTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/ShovelTypeRegistryModule.java new file mode 100644 index 0000000..5c35b06 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/ShovelTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.claim.ShovelType; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.claim.GDShovelType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class ShovelTypeRegistryModule implements CatalogRegistryModule<ShovelType> { + + private static ShovelTypeRegistryModule instance; + + public static ShovelTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, ShovelType> registryMap = new HashMap<>(); + + @Override + public Optional<ShovelType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<ShovelType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(ShovelTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final ShovelType type = new GDShovelType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(ShovelType type) { + // TODO Auto-generated method stub + + } + + static { + instance = new ShovelTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/TrustTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/TrustTypeRegistryModule.java new file mode 100644 index 0000000..d7a99de --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/TrustTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.claim.TrustType; +import com.griefdefender.api.claim.TrustTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.claim.GDTrustType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class TrustTypeRegistryModule implements CatalogRegistryModule<TrustType> { + + private static TrustTypeRegistryModule instance; + + public static TrustTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, TrustType> registryMap = new HashMap<>(); + + @Override + public Optional<TrustType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<TrustType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(TrustTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final TrustType type = new GDTrustType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(TrustType type) { + // TODO Auto-generated method stub + + } + + static { + instance = new TrustTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/registry/WeatherTypeRegistryModule.java b/sponge/src/main/java/com/griefdefender/registry/WeatherTypeRegistryModule.java new file mode 100644 index 0000000..2f742c1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/registry/WeatherTypeRegistryModule.java @@ -0,0 +1,91 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.registry; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.griefdefender.api.permission.option.type.WeatherType; +import com.griefdefender.api.permission.option.type.WeatherTypes; +import com.griefdefender.api.registry.CatalogRegistryModule; +import com.griefdefender.permission.option.type.GDWeatherType; +import com.griefdefender.util.RegistryHelper; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class WeatherTypeRegistryModule implements CatalogRegistryModule<WeatherType> { + + private static WeatherTypeRegistryModule instance; + + public static WeatherTypeRegistryModule getInstance() { + return instance; + } + + private final Map<String, WeatherType> registryMap = new HashMap<>(); + + @Override + public Optional<WeatherType> getById(String id) { + if (id == null) { + return Optional.empty(); + } + + if (id.contains("griefdefender.")) { + id = id.replace("griefdefender.", "griefdefender:"); + } + if (!id.contains(":")) { + id = "griefdefender:" + id; + } + + return Optional.ofNullable(this.registryMap.get(checkNotNull(id))); + } + + @Override + public Collection<WeatherType> getAll() { + return this.registryMap.values(); + } + + @Override + public void registerDefaults() { + RegistryHelper.mapFields(WeatherTypes.class, input -> { + final String name = input.toLowerCase().replace("_", "-"); + final String id = "griefdefender:" + name; + final WeatherType type = new GDWeatherType(id, name); + this.registryMap.put(id, type); + return type; + }); + } + + @Override + public void registerCustomType(WeatherType type) { + // TODO Auto-generated method stub + + } + + static { + instance = new WeatherTypeRegistryModule(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/storage/BaseStorage.java b/sponge/src/main/java/com/griefdefender/storage/BaseStorage.java new file mode 100644 index 0000000..0e37af5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/storage/BaseStorage.java @@ -0,0 +1,443 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.storage; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.flag.Flags; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.claim.GDClaimType; +import com.griefdefender.configuration.ClaimTemplateStorage; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.configuration.type.ConfigBase; +import com.griefdefender.configuration.type.GlobalConfig; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.event.GDRemoveClaimEvent; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.permission.GDPermissions; +import com.griefdefender.permission.flag.FlagContexts; +import com.griefdefender.permission.option.GDOption; +import com.griefdefender.registry.FlagRegistryModule; +import com.griefdefender.registry.OptionRegistryModule; +import com.griefdefender.util.PermissionUtil; +import com.griefdefender.util.SpongeContexts; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.service.permission.SubjectData; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.storage.WorldProperties; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +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.concurrent.ConcurrentHashMap; + +public abstract class BaseStorage { + + protected final Map<UUID, GDClaimManager> claimWorldManagers = new ConcurrentHashMap<>(); + + public static Map<UUID, GriefDefenderConfig<ConfigBase>> dimensionConfigMap = new HashMap<>(); + public static Map<UUID, GriefDefenderConfig<ConfigBase>> worldConfigMap = new HashMap<>(); + public static Map<String, ClaimTemplateStorage> globalTemplates = new HashMap<>(); + public static GriefDefenderConfig<GlobalConfig> globalConfig; + public static Map<UUID, GDPlayerData> GLOBAL_PLAYER_DATA = new ConcurrentHashMap<>(); + public static boolean USE_GLOBAL_PLAYER_STORAGE = true; + public static Map<String, Double> GLOBAL_OPTION_DEFAULTS = new HashMap<>(); + + public final static Path dataLayerFolderPath = GriefDefenderPlugin.getInstance().getConfigPath(); + public final static Path globalPlayerDataPath = dataLayerFolderPath.resolve("GlobalPlayerData"); + + public void initialize() throws Exception { + USE_GLOBAL_PLAYER_STORAGE = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.useGlobalPlayerDataStorage; + if (USE_GLOBAL_PLAYER_STORAGE) { + File globalPlayerDataFolder = globalPlayerDataPath.toFile(); + if (!globalPlayerDataFolder.exists()) { + globalPlayerDataFolder.mkdirs(); + } + } + + // handle default flag/option permissions + this.setDefaultGlobalPermissions(); + } + + public void clearCachedPlayerData(UUID worldUniqueId, UUID playerUniqueId) { + this.getClaimWorldManager(worldUniqueId).removePlayer(playerUniqueId); + } + + public abstract ClaimResult writeClaimToStorage(GDClaim claim); + + public abstract ClaimResult deleteClaimFromStorage(GDClaim claim); + + public Claim getClaim(UUID worldUniqueId, UUID id) { + return this.getClaimWorldManager(worldUniqueId).getClaimByUUID(id).orElse(null); + } + + public void asyncSaveGlobalPlayerData(UUID playerID, GDPlayerData playerData) { + // save everything except the ignore list + this.overrideSavePlayerData(playerID, playerData); + } + + abstract void overrideSavePlayerData(UUID playerID, GDPlayerData playerData); + + public ClaimResult createClaim(World world, Vector3i point1, Vector3i point2, ClaimType claimType, UUID ownerUniqueId, boolean cuboid) { + return createClaim(world, point1, point2, claimType, ownerUniqueId, cuboid, null); + } + + public ClaimResult createClaim(World world, Vector3i point1, Vector3i point2, ClaimType claimType, UUID ownerUniqueId, boolean cuboid, Claim parent) { + ClaimResult claimResult = Claim.builder() + .bounds(point1, point2) + .cuboid(cuboid) + .world(world.getUniqueId()) + .type(claimType) + .owner(ownerUniqueId) + .parent(parent) + .build(); + + if (claimResult.successful()) { + final Claim claim = claimResult.getClaim().get(); + final GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(world.getUniqueId()); + claimManager.addClaim(claim, true); + + /*if (claimResult.getClaims().size() > 1) { + claim.migrateClaims(new ArrayList<>(claimResult.getClaims())); + }*/ + } + + return claimResult; + } + + public ClaimResult deleteAllAdminClaims(CommandSource src, World world) { + GDClaimManager claimWorldManager = this.claimWorldManagers.get(world.getProperties().getUniqueId()); + if (claimWorldManager == null) { + return new GDClaimResult(ClaimResultType.CLAIMS_DISABLED); + } + + List<Claim> claimsToDelete = new ArrayList<Claim>(); + boolean adminClaimFound = false; + for (Claim claim : claimWorldManager.getWorldClaims()) { + if (claim.isAdminClaim()) { + claimsToDelete.add(claim); + adminClaimFound = true; + } + } + + if (!adminClaimFound) { + return new GDClaimResult(ClaimResultType.CLAIM_NOT_FOUND); + } + + GDCauseStackManager.getInstance().pushCause(src); + GDRemoveClaimEvent event = new GDRemoveClaimEvent(ImmutableList.copyOf(claimsToDelete)); + GriefDefender.getEventManager().post(event); + GDCauseStackManager.getInstance().popCause(); + if (event.cancelled()) { + return new GDClaimResult(ClaimResultType.CLAIM_EVENT_CANCELLED, + event.getMessage().orElse(GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.DELETE_ALL_TYPE_DENY, + ImmutableMap.of("type", TextComponent.of("ADMIN").color(TextColor.RED))))); + } + + for (Claim claim : claimsToDelete) { + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + claimWorldManager.deleteClaimInternal(claim, true); + } + + return new GDClaimResult(claimsToDelete, ClaimResultType.SUCCESS); + } + + public ClaimResult deleteClaim(Claim claim, boolean deleteChildren) { + GDClaimManager claimManager = this.getClaimWorldManager(claim.getWorldUniqueId()); + return claimManager.deleteClaim(claim, deleteChildren); + } + + public void abandonClaimsForPlayer(GDPermissionUser user, Set<Claim> claimsToDelete) { + for (Claim claim : claimsToDelete) { + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + GDClaimManager claimWorldManager = this.claimWorldManagers.get(claim.getWorldUniqueId()); + claimWorldManager.deleteClaimInternal(claim, true); + } + + return; + } + + public void deleteClaimsForPlayer(UUID playerID) { + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE && playerID != null) { + final GDPlayerData playerData = BaseStorage.GLOBAL_PLAYER_DATA.get(playerID); + List<Claim> claimsToDelete = new ArrayList<>(playerData.getInternalClaims()); + for (Claim claim : claimsToDelete) { + PermissionUtil.getInstance().clearPermissions((GDClaim) claim); + GDClaimManager claimWorldManager = this.claimWorldManagers.get(claim.getWorldUniqueId()); + claimWorldManager.deleteClaimInternal(claim, true); + } + + playerData.getInternalClaims().clear(); + return; + } + + for (GDClaimManager claimWorldManager : this.claimWorldManagers.values()) { + Set<Claim> claims = claimWorldManager.getInternalPlayerClaims(playerID); + if (playerID == null) { + claims = claimWorldManager.getWorldClaims(); + } + if (claims == null) { + continue; + } + + List<Claim> claimsToDelete = new ArrayList<Claim>(); + for (Claim claim : claims) { + if (!claim.isAdminClaim()) { + claimsToDelete.add(claim); + } + } + + for (Claim claim : claimsToDelete) { + PermissionUtil.getInstance().clearPermissions(GriefDefenderPlugin.DEFAULT_HOLDER, ImmutableSet.of(claim.getContext())); + claimWorldManager.deleteClaimInternal(claim, true); + claims.remove(claim); + } + } + } + + public GDClaim getClaimAt(Location<World> location) { + GDClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getUniqueId()); + return (GDClaim) claimManager.getClaimAt(location); + } + + public GDClaim getClaimAtPlayer(GDPlayerData playerData, Location<World> location) { + GDClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getUniqueId()); + return (GDClaim) claimManager.getClaimAtPlayer(location, playerData); + } + + public GDClaim getClaimAtPlayer(Location<World> location, GDPlayerData playerData, boolean useBorderBlockRadius) { + GDClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getUniqueId()); + return (GDClaim) claimManager.getClaimAt(location, null, playerData, useBorderBlockRadius); + } + + public GDClaim getClaimAtPlayer(Location<World> location, GDClaim cachedClaim, GDPlayerData playerData, boolean useBorderBlockRadius) { + GDClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getUniqueId()); + return (GDClaim) claimManager.getClaimAt(location, cachedClaim, playerData, useBorderBlockRadius); + } + + public GDClaim getClaimAt(Location<World> location, GDClaim cachedClaim) { + GDClaimManager claimManager = this.getClaimWorldManager(location.getExtent().getUniqueId()); + return (GDClaim) claimManager.getClaimAt(location, cachedClaim, null, false); + } + + public GDPlayerData getPlayerData(World world, UUID playerUniqueId) { + return this.getPlayerData(world.getUniqueId(), playerUniqueId); + } + + public GDPlayerData getPlayerData(UUID worldUniqueId, UUID playerUniqueId) { + GDPlayerData playerData = null; + GDClaimManager claimWorldManager = this.getClaimWorldManager(worldUniqueId); + playerData = claimWorldManager.getPlayerDataMap().get(playerUniqueId); + return playerData; + } + + public GDPlayerData getOrCreateGlobalPlayerData(UUID playerUniqueId) { + GDClaimManager claimWorldManager = this.getClaimWorldManager(null); + return claimWorldManager.getOrCreatePlayerData(playerUniqueId); + } + + public GDPlayerData getOrCreatePlayerData(World world, UUID playerUniqueId) { + return getOrCreatePlayerData(world.getUniqueId(), playerUniqueId); + } + + public GDPlayerData getOrCreatePlayerData(UUID worldUniqueId, UUID playerUniqueId) { + GDClaimManager claimWorldManager = this.getClaimWorldManager(worldUniqueId); + return claimWorldManager.getOrCreatePlayerData(playerUniqueId); + } + + public void removePlayerData(UUID worldUniqueId, UUID playerUniqueId) { + GDClaimManager claimWorldManager = this.getClaimWorldManager(worldUniqueId); + claimWorldManager.removePlayer(playerUniqueId); + } + + public GDClaimManager getClaimWorldManager(UUID worldUniqueId) { + GDClaimManager claimWorldManager = null; + if (worldUniqueId == null) { + worldUniqueId = Sponge.getServer().getDefaultWorld().get().getUniqueId(); + } + claimWorldManager = this.claimWorldManagers.get(worldUniqueId); + + if (claimWorldManager == null) { + final WorldProperties defaultWorldProperties = Sponge.getServer().getDefaultWorld().get(); + final World world = Sponge.getServer().getWorld(worldUniqueId).orElse(Sponge.getServer().getWorld(defaultWorldProperties.getUniqueId()).get()); + registerWorld(world); + claimWorldManager = this.claimWorldManagers.get(world.getUniqueId()); + } + return claimWorldManager; + } + + public void removeClaimWorldManager(WorldProperties worldProperties) { + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + return; + } + this.claimWorldManagers.remove(worldProperties.getUniqueId()); + } + + public void setDefaultGlobalPermissions() { + // Admin defaults + Set<Context> contexts = new HashSet<>(); + contexts.add(ClaimContexts.ADMIN_DEFAULT_CONTEXT); + final GriefDefenderConfig<GlobalConfig> activeConfig = GriefDefenderPlugin.getGlobalConfig(); + final Map<String, Boolean> adminDefaultFlags = activeConfig.getConfig().permissionCategory.getFlagDefaults(ClaimTypes.ADMIN.getName().toLowerCase()); + if (adminDefaultFlags != null && !adminDefaultFlags.isEmpty()) { + this.setDefaultFlags(contexts, adminDefaultFlags); + } + + // Basic defaults + contexts = new HashSet<>(); + contexts.add(ClaimContexts.BASIC_DEFAULT_CONTEXT); + final Map<String, Boolean> basicDefaultFlags = activeConfig.getConfig().permissionCategory.getFlagDefaults(ClaimTypes.BASIC.getName().toLowerCase()); + if (basicDefaultFlags != null && !basicDefaultFlags.isEmpty()) { + this.setDefaultFlags(contexts, basicDefaultFlags); + } + final Map<String, String> basicDefaultOptions = activeConfig.getConfig().permissionCategory.getBasicOptionDefaults(); + contexts = new HashSet<>(); + contexts.add(ClaimTypes.BASIC.getDefaultContext()); + this.setDefaultOptions(ClaimTypes.BASIC.toString(), contexts, new HashMap<>(basicDefaultOptions)); + + // Town defaults + contexts = new HashSet<>(); + contexts.add(ClaimContexts.TOWN_DEFAULT_CONTEXT); + final Map<String, Boolean> townDefaultFlags = activeConfig.getConfig().permissionCategory.getFlagDefaults(ClaimTypes.TOWN.getName().toLowerCase()); + final Map<String, String> townDefaultOptions = activeConfig.getConfig().permissionCategory.getTownOptionDefaults(); + if (townDefaultFlags != null && !townDefaultFlags.isEmpty()) { + this.setDefaultFlags(contexts, townDefaultFlags); + } + contexts = new HashSet<>(); + contexts.add(ClaimTypes.TOWN.getDefaultContext()); + this.setDefaultOptions(ClaimTypes.TOWN.toString(), contexts, new HashMap<>(townDefaultOptions)); + + // Subdivision defaults + contexts = new HashSet<>(); + contexts.add(ClaimTypes.SUBDIVISION.getDefaultContext()); + final Map<String, String> subdivisionDefaultOptions = activeConfig.getConfig().permissionCategory.getSubdivisionOptionDefaults(); + this.setDefaultOptions(ClaimTypes.SUBDIVISION.toString(), contexts, new HashMap<>(subdivisionDefaultOptions)); + + // Wilderness defaults + contexts = new HashSet<>(); + contexts.add(ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); + final Map<String, Boolean> wildernessDefaultFlags = activeConfig.getConfig().permissionCategory.getFlagDefaults(ClaimTypes.WILDERNESS.getName().toLowerCase()); + this.setDefaultFlags(contexts, wildernessDefaultFlags); + + // Global default options + contexts = new HashSet<>(); + contexts.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + final Map<String, Boolean> globalDefaultFlags = activeConfig.getConfig().permissionCategory.getFlagDefaults("global"); + this.setDefaultFlags(contexts, globalDefaultFlags); + final Map<String, String> globalDefaultOptions = activeConfig.getConfig().permissionCategory.getUserOptionDefaults(); + this.setDefaultOptions(ClaimContexts.GLOBAL_DEFAULT_CONTEXT.getName(), contexts, new HashMap<>(globalDefaultOptions)); + GriefDefenderPlugin.getInstance().getPermissionProvider().setTransientPermission(GriefDefenderPlugin.DEFAULT_HOLDER, "griefdefender", false, new HashSet<>()); + activeConfig.save(); + } + + private void setDefaultFlags(Set<Context> contexts, Map<String, Boolean> defaultFlags) { + GriefDefenderPlugin.getInstance().executor.execute(() -> { + for (Map.Entry<String, Boolean> mapEntry : defaultFlags.entrySet()) { + final Flag flag = FlagRegistryModule.getInstance().getById(mapEntry.getKey()).orElse(null); + if (flag == null) { + continue; + } + PermissionUtil.getInstance().setTransientPermission(GriefDefenderPlugin.DEFAULT_HOLDER, GDPermissions.FLAG_BASE + "." + mapEntry.getKey(), mapEntry.getValue(), contexts); + if (flag == Flags.ENTITY_DAMAGE) { + // allow monsters to be attacked by default + contexts.add(FlagContexts.TARGET_TYPE_MONSTER); + PermissionUtil.getInstance().setTransientPermission(GriefDefenderPlugin.DEFAULT_HOLDER, GDPermissions.FLAG_BASE + "." + mapEntry.getKey(), true, contexts); + contexts.remove(FlagContexts.TARGET_TYPE_MONSTER); + } + } + PermissionUtil.getInstance().refreshCachedData(GriefDefenderPlugin.DEFAULT_HOLDER); + }); + } + + private void setDefaultOptions(String type, Set<Context> contexts, Map<String, String> defaultOptions) { + final Map<Set<Context>, Map<String, String>> permanentOptions = PermissionUtil.getInstance().getPermanentOptions(GriefDefenderPlugin.DEFAULT_HOLDER); + final Map<String, String> options = permanentOptions.get(contexts); + GriefDefenderPlugin.getInstance().executor.execute(() -> { + for (Map.Entry<String, String> optionEntry : defaultOptions.entrySet()) { + final Option option = OptionRegistryModule.getInstance().getById(optionEntry.getKey()).orElse(null); + if (option == null) { + continue; + } + + if (!((GDOption) option).validateStringValue(optionEntry.getValue(), true)) { + continue; + } + // Transient options are checked first so we must ignore setting if a persisted option exists + boolean foundPersisted = false; + if (options != null) { + for (Entry<String, String> mapEntry : options.entrySet()) { + if (mapEntry.getKey().equalsIgnoreCase(option.getPermission())) { + foundPersisted = true; + break; + } + } + if (foundPersisted) { + continue; + } + } + PermissionUtil.getInstance().setTransientOption(GriefDefenderPlugin.DEFAULT_HOLDER, option.getPermission(), optionEntry.getValue(), contexts); + } + PermissionUtil.getInstance().refreshCachedData(GriefDefenderPlugin.DEFAULT_HOLDER); + }); + } + + abstract GDPlayerData getPlayerDataFromStorage(UUID playerID); + + public abstract void registerWorld(World world); + + public abstract void loadWorldData(World world); + + public abstract void unloadWorldData(World world); + + abstract void loadClaimTemplates(); +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/storage/FileStorage.java b/sponge/src/main/java/com/griefdefender/storage/FileStorage.java new file mode 100644 index 0000000..07f0f93 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/storage/FileStorage.java @@ -0,0 +1,512 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.storage; + +import com.flowpowered.math.vector.Vector3i; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimBlockSystem; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimSchematic; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.claim.GDClaimSchematic; +import com.griefdefender.configuration.ClaimStorageData; +import com.griefdefender.configuration.ClaimTemplateStorage; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.TownStorageData; +import com.griefdefender.configuration.type.ConfigBase; +import com.griefdefender.event.GDLoadClaimEvent; +import com.griefdefender.migrator.GPBukkitMigrator; +import com.griefdefender.migrator.WorldGuardMigrator; +import org.apache.commons.io.FileUtils; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.DataContainer; +import org.spongepowered.api.data.persistence.DataFormats; +import org.spongepowered.api.data.persistence.DataTranslators; +import org.spongepowered.api.scheduler.Task; +import org.spongepowered.api.world.DimensionType; +import org.spongepowered.api.world.World; +import org.spongepowered.api.world.schematic.Schematic; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.zip.GZIPInputStream; + +public class FileStorage extends BaseStorage { + + private final static Path migrationVersionFilePath = dataLayerFolderPath.resolve("_migrationVersion"); + private final static Path worldsConfigFolderPath = dataLayerFolderPath.resolve("worlds"); + public final static Path claimDataPath = Paths.get("GriefDefenderData", "ClaimData"); + public final static Path claimTemplatePath = claimDataPath.resolve("Templates"); + private final static Map<UUID, Task> cleanupClaimTasks = new HashMap<>(); + private final Path rootConfigPath = GriefDefenderPlugin.getInstance().getConfigPath().resolve("worlds"); + public static Path rootWorldSavePath; + private int claimLoadCount = 0; + + @Override + public void initialize() throws Exception { + // ensure data folders exist + File worldsDataFolder = worldsConfigFolderPath.toFile(); + + if (!worldsDataFolder.exists()) { + worldsDataFolder.mkdirs(); + } + + rootWorldSavePath = Sponge.getGame().getSavesDirectory().resolve(Sponge.getServer().getDefaultWorldName()); + + super.initialize(); + } + + @Override + public void loadClaimTemplates() { + try { + if (Files.exists(rootWorldSavePath.resolve(claimTemplatePath))) { + File[] files = rootWorldSavePath.resolve(claimTemplatePath).toFile().listFiles(); + int count = 0; + for (File file : files) { + ClaimTemplateStorage templateStorage = new ClaimTemplateStorage(file.toPath()); + String templateName = templateStorage.getConfig().getTemplateName(); + if (!templateName.isEmpty()) { + globalTemplates.put(templateName, templateStorage); + count++; + } + } + GriefDefenderPlugin.getInstance().getLogger().info(count + " total claim templates loaded."); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void registerWorld(World world) { + final UUID worldUniqueId = world.getUniqueId(); + DimensionType dimType = world.getProperties().getDimensionType(); + String[] parts = dimType.getId().split(":"); + Path dimPath = rootConfigPath.resolve(parts[0]).resolve(dimType.getName()); + if (!Files.exists(dimPath.resolve(world.getName()))) { + try { + Files.createDirectories(dimPath.resolve(world.getName())); + } catch (IOException e) { + e.printStackTrace(); + } + } + + GDClaimManager claimWorldManager = new GDClaimManager(world); + this.claimWorldManagers.put(world.getUniqueId(), claimWorldManager); + // create/load configs + GriefDefenderConfig<ConfigBase> dimConfig = new GriefDefenderConfig<>(ConfigBase.class, dimPath.resolve("dimension.conf"), BaseStorage.globalConfig); + GriefDefenderConfig<ConfigBase> worldConfig = new GriefDefenderConfig<>(ConfigBase.class, dimPath.resolve(world.getName()).resolve("world.conf"), dimConfig); + BaseStorage.dimensionConfigMap.put(worldUniqueId, dimConfig); + BaseStorage.worldConfigMap.put(worldUniqueId, worldConfig); + + if (GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.gpBukkitMigrator) { + final File migrateFile = dimPath.resolve(world.getName()).resolve("_bukkitMigrated").toFile(); + if (!migrateFile.exists()) { + try { + final Path path = Paths.get("plugins", "GriefPreventionData", "ClaimData"); + if (path.toFile().exists()) { + GPBukkitMigrator.migrate(world, path); + Files.createFile(dimPath.resolve(world.getName()).resolve("_bukkitMigrated")); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + if (GriefDefenderPlugin.getGlobalConfig().getConfig().migrator.worldGuardMigrator) { + final File migrateFile = dimPath.resolve(world.getName()).resolve("_wgMigrated").toFile(); + if (!migrateFile.exists()) { + try { + final Path path = Paths.get("plugins", "WorldGuard", "worlds", world.getName()); + if (path.toFile().exists()) { + WorldGuardMigrator.migrate(world); + Files.createFile(dimPath.resolve(world.getName()).resolve("_wgMigrated")); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + Path newWorldDataPath = dimPath.resolve(world.getName()); + + try { + // Create data folders if they do not exist + if (Files.notExists(newWorldDataPath.resolve("ClaimData"))) { + Files.createDirectories(newWorldDataPath.resolve("ClaimData")); + } + if (Files.notExists(newWorldDataPath.resolve("ClaimData").resolve("wilderness"))) { + Files.createDirectories(newWorldDataPath.resolve("ClaimData").resolve("wilderness")); + } + if (Files.notExists(newWorldDataPath.resolve("SchematicData"))) { + Files.createDirectories(newWorldDataPath.resolve("SchematicData")); + } + + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().getSchematicWorldMap().put(world.getUniqueId(), newWorldDataPath.resolve("SchematicData")); + } + + if (BaseStorage.USE_GLOBAL_PLAYER_STORAGE) { + if (Files.notExists(globalPlayerDataPath)) { + Files.createDirectories(globalPlayerDataPath); + } + } else if (Files.notExists(newWorldDataPath.resolve("PlayerData"))) { + Files.createDirectories(newWorldDataPath.resolve("PlayerData")); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void loadWorldData(World world) { + final UUID worldUniqueId = world.getUniqueId(); + final DimensionType dimType = world.getProperties().getDimensionType(); + final String[] parts = dimType.getId().split(":"); + final Path dimPath = rootConfigPath.resolve(parts[0]).resolve(dimType.getName()); + final Path newWorldDataPath = dimPath.resolve(world.getName()); + GDClaimManager claimWorldManager = this.claimWorldManagers.get(worldUniqueId); + if (claimWorldManager == null) { + this.registerWorld(world); + claimWorldManager = this.claimWorldManagers.get(worldUniqueId); + } + + // Load wilderness claim first + final Path wildernessFilePath = newWorldDataPath.resolve("ClaimData").resolve("wilderness").resolve(worldUniqueId.toString()); + if (Files.exists(wildernessFilePath)) { + try { + this.loadClaim(wildernessFilePath.toFile(), world, world.getUniqueId()); + } catch (Exception e) { + e.printStackTrace(); + } + } else { + claimWorldManager.createWildernessClaim(world); + } + + // Load Claim Data + try { + File[] files = newWorldDataPath.resolve("ClaimData").toFile().listFiles(); + if (files != null && files.length > 0) { + this.loadClaimData(files, world); + GriefDefenderPlugin.getInstance().getLogger().info("[" + world.getName() + "] " + this.claimLoadCount + " total claims loaded."); + } + + if (GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.useGlobalPlayerDataStorage) { + files = globalPlayerDataPath.toFile().listFiles(); + } else { + files = newWorldDataPath.resolve("PlayerData").toFile().listFiles(); + } + if (files != null && files.length > 0) { + this.loadPlayerData(world, files); + } + + // If a wilderness claim was not loaded, create a new one + if (claimWorldManager.getWildernessClaim() == null) { + claimWorldManager.createWildernessClaim(world); + } + + // Load schematics + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getLogger().info("Loading schematics for world " + world.getName() + "..."); + GriefDefenderPlugin.getInstance().getWorldEditProvider().loadSchematics(world); + } + } catch (Exception e) { + e.printStackTrace(); + } + + this.claimLoadCount = 0; + } + + public void unloadWorldData(World world) { + final UUID worldUniqueId = world.getUniqueId(); + GDClaimManager claimWorldManager = this.getClaimWorldManager(worldUniqueId); + for (Claim claim : claimWorldManager.getWorldClaims()) { + ((GDClaim) claim).unload(); + } + // Task must be cancelled before removing the claimWorldManager reference to avoid a memory leak + Task cleanupTask = cleanupClaimTasks.get(worldUniqueId); + if (cleanupTask != null) { + cleanupTask.cancel(); + cleanupClaimTasks.remove(worldUniqueId); + } + + claimWorldManager.unload(); + this.claimWorldManagers.remove(worldUniqueId); + BaseStorage.dimensionConfigMap.remove(worldUniqueId); + BaseStorage.worldConfigMap.remove(worldUniqueId); + } + + void loadClaimData(File[] files, World world) throws Exception { + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + this.loadClaimFile(file, world); + } + } + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isDirectory()) { + this.loadClaimData(file.listFiles(), world); + } + } + } + + void loadClaimFile(File file, World world) { + if (file.isFile()) // avoids folders + { + // the filename is the claim ID. try to parse it + UUID claimId; + + try { + final String fileName = file.getName(); + // UUID's should always be 36 in length + if (fileName.length() != 36) { + return; + } + + claimId = UUID.fromString(fileName); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Could not read claim file " + file.getAbsolutePath()); + return; + } + + try { + this.loadClaim(file, world, claimId); + } + + catch (Exception e) { + if (e.getMessage() != null && e.getMessage().contains("World not found")) { + file.delete(); + } else { + GriefDefenderPlugin.getInstance().getLogger().error(file.getName() + " is corrupted."); + e.printStackTrace(); + } + } + } + } + + void loadPlayerData(World world, File[] files) throws Exception { + final boolean resetMigration = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetMigrations; + final boolean resetClaimData = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.resetAccruedClaimBlocks; + final int migration2dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateAreaRate; + final int migration3dRate = GriefDefenderPlugin.getGlobalConfig().getConfig().playerdata.migrateVolumeRate; + boolean migrate = false; + if (resetMigration || resetClaimData || (migration2dRate > -1 && GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.AREA) + || (migration3dRate > -1 && GriefDefenderPlugin.CLAIM_BLOCK_SYSTEM == ClaimBlockSystem.VOLUME)) { + // load all player data if migrating + migrate = true; + } + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) + { + UUID playerUUID; + + try { + final String fileName = files[i].getName(); + // UUID's should always be 36 in length + if (fileName.length() != 36) { + return; + } + + playerUUID = UUID.fromString(fileName); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error("Could not read player file " + files[i].getAbsolutePath()); + continue; + } + + if (!migrate && !Sponge.getServer().getPlayer(playerUUID).isPresent()) { + continue; + } + + try { + this.getOrCreatePlayerData(world, playerUUID); + } catch (Exception e) { + if (e.getMessage() != null && e.getMessage().contains("World not found")) { + files[i].delete(); + } else { + GriefDefenderPlugin.getInstance().getLogger().error(files[i].getName() + " is corrupted."); + e.printStackTrace(); + } + } + } + } + } + + public GDClaim loadClaim(File claimFile, World world, UUID claimId) + throws Exception { + GDClaim claim; + + final GDClaimManager claimManager = this.getClaimWorldManager(world.getUniqueId()); + if (claimManager.getWildernessClaim() != null && claimManager.getWildernessClaim().getUniqueId().equals(claimId)) { + return null; + } + boolean isTown = claimFile.toPath().getParent().endsWith("town"); + boolean writeToStorage = false; + ClaimStorageData claimStorage = null; + if (isTown) { + claimStorage = new TownStorageData(claimFile.toPath(), world.getUniqueId()); + } else { + claimStorage = new ClaimStorageData(claimFile.toPath(), world.getUniqueId()); + } + + final ClaimType type = claimStorage.getConfig().getType(); + final UUID parent = claimStorage.getConfig().getParent().orElse(null); + final String fileName = claimFile.getName(); + //final World world = Sponge.getServer().loadWorld(worldProperties).orElse(null); + //if (world == null) { + // throw new Exception("World [Name: " + worldProperties.getWorldName() + "][UUID: " + worldUniqueId.toString() + "] is not loaded."); + //} + + if (claimFile.getParentFile().getName().equalsIgnoreCase("claimdata")) { + final Path newPath = claimStorage.filePath.getParent().resolve(type.getName().toLowerCase()); + if (Files.notExists(newPath)) { + Files.createDirectories(newPath); + } + Files.move(claimStorage.filePath, newPath.resolve(fileName)); + claimStorage.filePath = newPath.resolve(fileName); + claimStorage = new ClaimStorageData(claimStorage.filePath, world.getUniqueId()); + } + + // identify world the claim is in + UUID claimWorldUniqueId = claimStorage.getConfig().getWorldUniqueId(); + if (!claimWorldUniqueId.equals(world.getUniqueId())) { + GriefDefenderPlugin.getInstance().getLogger().info("Found mismatch world UUID in " + type.getName().toLowerCase() + " claim file " + claimFile + ". Expected " + world.getUniqueId() + ", found " + claimWorldUniqueId + ". Updating file with correct UUID..."); + claimStorage.getConfig().setWorldUniqueId(world.getUniqueId()); + writeToStorage = true; + } + + // boundaries + final boolean cuboid = claimStorage.getConfig().isCuboid(); + Vector3i lesserCorner = claimStorage.getConfig().getLesserBoundaryCornerPos(); + Vector3i greaterCorner = claimStorage.getConfig().getGreaterBoundaryCornerPos(); + if (lesserCorner == null || greaterCorner == null) { + throw new Exception("Claim file '" + claimFile.getName() + "' has corrupted data and cannot be loaded. Skipping..."); + } + + UUID ownerID = claimStorage.getConfig().getOwnerUniqueId(); + claim = new GDClaim(world, lesserCorner, greaterCorner, claimId, claimStorage.getConfig().getType(), ownerID, cuboid); + claim.setClaimStorage(claimStorage); + claim.setClaimData(claimStorage.getConfig()); + GDLoadClaimEvent.Pre preEvent = new GDLoadClaimEvent.Pre(claim); + GriefDefender.getEventManager().post(preEvent); + + // add parent claim first + if (parent != null) { + GDClaim parentClaim = null; + try { + parentClaim = (GDClaim) claimManager.getClaimByUUID(parent).orElse(null); + } catch (Throwable t) { + t.printStackTrace(); + } + if (parentClaim == null) { + throw new Exception("Required parent claim '" + parent + " no longer exists. Skipping..."); + } + claim.parent = parentClaim; + } + + claimManager.addClaim(claim, writeToStorage); + this.claimLoadCount++; + GDLoadClaimEvent.Post postEvent = new GDLoadClaimEvent.Post(claim); + GriefDefender.getEventManager().post(postEvent); + return claim; + } + + @Override + public ClaimResult writeClaimToStorage(GDClaim claim) { + try { + ClaimStorageData claimStorage = claim.getClaimStorage(); + claim.updateClaimStorageData(); + claimStorage.save(); + return new GDClaimResult(claim, ClaimResultType.SUCCESS); + } catch (Exception e) { + GriefDefenderPlugin.getInstance().getLogger().error(claim.getUniqueId() + " could not save properly."); + e.printStackTrace(); + } + + return new GDClaimResult(claim, ClaimResultType.FAILURE); + } + + @Override + public ClaimResult deleteClaimFromStorage(GDClaim claim) { + final GDPlayerData ownerData = claim.getOwnerPlayerData(); + try { + Files.delete(claim.getClaimStorage().filePath); + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + final Path schematicPath = GriefDefenderPlugin.getInstance().getWorldEditProvider().getSchematicWorldMap().get(claim.getWorldUniqueId()); + if (schematicPath != null && Files.exists(schematicPath.resolve(claim.getUniqueId().toString()))) { + if (ownerData != null && ownerData.useRestoreSchematic) { + final ConfigBase activeConfig = GriefDefenderPlugin.getActiveConfig(claim.getWorldUniqueId()).getConfig(); + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null && activeConfig.claim.claimAutoSchematicRestore) { + final ClaimSchematic schematic = claim.getSchematics().get("__restore__"); + if (schematic != null) { + schematic.apply(); + } + } + } + FileUtils.deleteDirectory(schematicPath.resolve(claim.getUniqueId().toString()).toFile()); + } + } + + return new GDClaimResult(claim, ClaimResultType.SUCCESS); + } catch (IOException e) { + e.printStackTrace(); + GriefDefenderPlugin.getInstance().getLogger().error("Error: Unable to delete claim file \"" + claim.getClaimStorage().filePath + "\"."); + } + + return new GDClaimResult(claim, ClaimResultType.FAILURE); + } + + @Override + GDPlayerData getPlayerDataFromStorage(UUID playerID) { + return null; + } + + @Override + void overrideSavePlayerData(UUID playerID, GDPlayerData playerData) { + } + +} diff --git a/sponge/src/main/java/com/griefdefender/task/ClaimBlockTask.java b/sponge/src/main/java/com/griefdefender/task/ClaimBlockTask.java new file mode 100644 index 0000000..50fc4bc --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/ClaimBlockTask.java @@ -0,0 +1,122 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.configuration.PlayerStorageData; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.storage.BaseStorage; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.manipulator.mutable.entity.VehicleData; +import org.spongepowered.api.data.property.block.MatterProperty; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.world.Location; +import org.spongepowered.api.world.World; + +import java.math.BigDecimal; +import java.util.Optional; + +public class ClaimBlockTask implements Runnable { + + private Player player; + + public ClaimBlockTask() { + } + + public ClaimBlockTask(Player player) { + this.player = player; + } + + @Override + public void run() { + if (this.player == null) { + for (World world : Sponge.getServer().getWorlds()) { + int i = 0; + for (Entity entity : world.getEntities()) { + if (!(entity instanceof Player)) { + continue; + } + + final Player player = (Player) entity; + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + final int accrualPerHour = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), player, Options.BLOCKS_ACCRUED_PER_HOUR, claim); + if (accrualPerHour > 0) { + ClaimBlockTask newTask = new ClaimBlockTask(player); + Sponge.getGame().getScheduler().createTaskBuilder().delayTicks(i++).execute(newTask) + .submit(GDBootstrap.getInstance()); + } + } + } + return; + } + + final BaseStorage dataStore = GriefDefenderPlugin.getInstance().dataStore; + final GDPlayerData playerData = dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final Location<World> lastLocation = playerData.lastAfkCheckLocation; + final Optional<MatterProperty> matterProperty = player.getLocation().getBlock().getProperty(MatterProperty.class); + if (!player.get(VehicleData.class).isPresent() && + (lastLocation == null || lastLocation.getPosition().distanceSquared(player.getLocation().getPosition()) >= 0) && + matterProperty.isPresent() && matterProperty.get().getValue() != MatterProperty.Matter.LIQUID) { + int accruedBlocks = playerData.getBlocksAccruedPerHour() / 12; + if (accruedBlocks < 0) { + accruedBlocks = 1; + } + + if (GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(player.getUniqueId()).orElse(null); + if (playerAccount == null) { + return; + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(accruedBlocks), Sponge.getCauseStackManager().getCurrentCause()); + } else { + int currentTotal = playerData.getAccruedClaimBlocks(); + if ((currentTotal + accruedBlocks) > playerData.getMaxAccruedClaimBlocks()) { + PlayerStorageData playerStorage = playerData.getStorageData(); + playerStorage.getConfig().setAccruedClaimBlocks(playerData.getMaxAccruedClaimBlocks()); + playerData.lastAfkCheckLocation = player.getLocation(); + return; + } + + PlayerStorageData playerStorage = playerData.getStorageData(); + playerStorage.getConfig().setAccruedClaimBlocks(playerStorage.getConfig().getAccruedClaimBlocks() + accruedBlocks); + } + } + + playerData.lastAfkCheckLocation = player.getLocation(); + } +} diff --git a/sponge/src/main/java/com/griefdefender/task/ClaimCleanupTask.java b/sponge/src/main/java/com/griefdefender/task/ClaimCleanupTask.java new file mode 100644 index 0000000..c410677 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/ClaimCleanupTask.java @@ -0,0 +1,152 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimSchematic; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.util.PermissionUtil; +import net.kyori.text.Component; +import net.kyori.text.serializer.plain.PlainComponentSerializer; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.world.storage.WorldProperties; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class ClaimCleanupTask implements Runnable { + + @Override + public void run() { + for (WorldProperties worldProperties : Sponge.getServer().getAllWorldProperties()) { + GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(worldProperties.getUniqueId()); + Set<Claim> claimList = claimManager.getWorldClaims(); + if (claimList.size() == 0) { + continue; + } + + final GriefDefenderConfig<?> activeConfig = GriefDefenderPlugin.getActiveConfig(worldProperties.getUniqueId()); + final boolean schematicRestore = activeConfig.getConfig().claim.claimAutoSchematicRestore; + Iterator<Claim> iterator = new HashSet<>(claimList).iterator(); + while (iterator.hasNext()) { + GDClaim claim = (GDClaim) iterator.next(); + final GDPlayerData playerData = claim.getOwnerPlayerData(); + if (claim.isAdminClaim() || !claim.getInternalClaimData().allowExpiration() || playerData == null) { + continue; + } + + if (!playerData.dataInitialized) { + continue; + } + + int areaOfDefaultClaim = 0; + if (activeConfig.getConfig().claim.autoChestClaimBlockRadius >= 0) { + areaOfDefaultClaim = (int) Math.pow(activeConfig.getConfig().claim.autoChestClaimBlockRadius * 2 + 1, 2); + } + + final GDPermissionHolder subject = playerData.getSubject(); + Instant claimLastActive = claim.getInternalClaimData().getDateLastActive(); + + try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + final int claimExpirationChest = playerData.getChestClaimExpiration(); + if (claim.getArea() <= areaOfDefaultClaim && claimExpirationChest > 0) { + if (claimLastActive.plus(Duration.ofDays(claimExpirationChest)) + .isBefore(Instant.now())) { + playerData.useRestoreSchematic = schematicRestore; + claimManager.deleteClaim(claim); + playerData.useRestoreSchematic = false; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_EXPIRED_INACTIVITY, + ImmutableMap.of( + "player", subject.getFriendlyName(), + "uuid", claim.getUniqueId().toString())); + GriefDefenderPlugin.getInstance().getLogger().info(PlainComponentSerializer.INSTANCE.serialize(message)); + if (!schematicRestore && activeConfig.getConfig().claim.claimAutoNatureRestore) { + BlockUtil.getInstance().restoreClaim(claim); + } + // remove all context permissions + PermissionUtil.getInstance().clearPermissions(claim); + } + return; + } + + if (!claim.isBasicClaim()) { + continue; + } + final int optionValue = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), subject, Options.EXPIRATION, claim); + final int optionClaimExpirationBasic = optionValue; + if (optionClaimExpirationBasic > 0) { + final Instant localNow = Instant.now(); + final boolean claimNotActive = claimLastActive.plus(Duration.ofDays(optionClaimExpirationBasic)).isBefore(localNow); + if (!claimNotActive) { + final boolean taxEnabled = activeConfig.getConfig().claim.bankTaxSystem; + if (!taxEnabled || !claim.getData().isExpired()) { + continue; + } + final Instant taxPastDueDate = claim.getEconomyData().getTaxPastDueDate().orElse(null); + if (taxPastDueDate == null) { + continue; + } + + final int taxExpirationDays = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), subject, Options.TAX_EXPIRATION, claim).intValue(); + final int expireDaysToKeep = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), subject, Options.TAX_EXPIRATION_DAYS_KEEP, claim).intValue(); + if (!taxPastDueDate.plus(Duration.ofDays(taxExpirationDays + expireDaysToKeep)).isBefore(localNow)) { + continue; + } + } + } + + playerData.useRestoreSchematic = schematicRestore; + claimManager.deleteClaim(claim); + playerData.useRestoreSchematic = false; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CLAIM_EXPIRED_INACTIVITY, + ImmutableMap.of( + "player", subject.getFriendlyName(), + "uuid", claim.getUniqueId().toString())); + GriefDefenderPlugin.getInstance().getLogger().info(PlainComponentSerializer.INSTANCE.serialize(message)); + if (!schematicRestore && activeConfig.getConfig().claim.claimAutoNatureRestore) { + BlockUtil.getInstance().restoreClaim(claim); + } + // remove all context permissions + PermissionUtil.getInstance().clearPermissions(claim); + } + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/task/ClaimVisualApplyTask.java b/sponge/src/main/java/com/griefdefender/task/ClaimVisualApplyTask.java new file mode 100644 index 0000000..79129d1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/ClaimVisualApplyTask.java @@ -0,0 +1,86 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.internal.visual.ClaimVisual; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.BlockSnapshot; +import org.spongepowered.api.entity.living.player.Player; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +public class ClaimVisualApplyTask implements Runnable { + + private ClaimVisual visualization; + private Player player; + private GDPlayerData playerData; + private boolean resetActive; + + public ClaimVisualApplyTask(Player player, GDPlayerData playerData, ClaimVisual visualization) { + this(player, playerData, visualization, true); + } + + public ClaimVisualApplyTask(Player player, GDPlayerData playerData, ClaimVisual visualization, boolean resetActive) { + this.visualization = visualization; + this.playerData = playerData; + this.player = player; + this.resetActive = resetActive; + } + + @Override + public void run() { + // Only revert active visual if we are not currently creating a claim + if (!this.playerData.visualBlocks.isEmpty() && this.playerData.lastShovelLocation == null) { + if (this.resetActive) { + this.playerData.revertActiveVisual(this.player); + } + } + + for (int i = 0; i < this.visualization.getVisualTransactions().size(); i++) { + BlockSnapshot snapshot = this.visualization.getVisualTransactions().get(i).getFinal(); + this.player.sendBlockChange(snapshot.getPosition(), snapshot.getState()); + } + + if (this.visualization.getClaim() != null) { + this.playerData.visualClaimId = this.visualization.getClaim().getUniqueId(); + this.visualization.getClaim().playersWatching.add(this.player.getUniqueId()); + } + // If we still have active visuals to revert, combine with new + if (!this.playerData.visualBlocks.isEmpty()) { + this.playerData.visualBlocks.addAll(this.visualization.getVisualTransactions()); + } else { + this.playerData.visualBlocks = new ArrayList<>(this.visualization.getVisualTransactions()); + } + + if (playerData.lastShovelLocation == null) { + this.playerData.visualRevertTask = Sponge.getGame().getScheduler().createTaskBuilder().delay(1, TimeUnit.MINUTES) + .execute(new ClaimVisualRevertTask(this.player, this.playerData)).submit(GDBootstrap.getInstance()); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/task/ClaimVisualRevertTask.java b/sponge/src/main/java/com/griefdefender/task/ClaimVisualRevertTask.java new file mode 100644 index 0000000..77f8ce1 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/ClaimVisualRevertTask.java @@ -0,0 +1,55 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import org.spongepowered.api.entity.living.player.Player; + +class ClaimVisualRevertTask implements Runnable { + + private Player player; + private GDPlayerData playerData; + + public ClaimVisualRevertTask(Player player, GDPlayerData playerData) { + this.playerData = playerData; + this.player = player; + } + + @Override + public void run() { + // don't do anything if the player's current visualization is different + // from the one scheduled to revert + if (this.playerData.visualBlocks.isEmpty()) { + return; + } + + // check for any active WECUI visuals + if (GriefDefenderPlugin.getInstance().getWorldEditProvider() != null) { + GriefDefenderPlugin.getInstance().getWorldEditProvider().revertVisuals(this.player, this.playerData, this.playerData.visualClaimId); + } + this.playerData.revertActiveVisual(this.player); + } +} diff --git a/sponge/src/main/java/com/griefdefender/task/PlayerTickTask.java b/sponge/src/main/java/com/griefdefender/task/PlayerTickTask.java new file mode 100644 index 0000000..16a0fdf --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/PlayerTickTask.java @@ -0,0 +1,101 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.permission.GDPermissionManager; + +import net.kyori.text.TextComponent; +import net.kyori.text.adapter.spongeapi.TextAdapter; +import net.kyori.text.format.TextColor; + +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.key.Keys; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.gamemode.GameMode; +import org.spongepowered.api.entity.living.player.gamemode.GameModes; +import org.spongepowered.api.world.World; + +public class PlayerTickTask implements Runnable { + + public PlayerTickTask() { + + } + + @Override + public void run() { + for (World world : Sponge.getServer().getWorlds()) { + for (Player player : world.getPlayers()) { + if (player.isRemoved()) { + continue; + } + final GDPlayerData playerData = GriefDefenderPlugin.getInstance().dataStore.getOrCreatePlayerData(player.getWorld(), player.getUniqueId()); + final GDClaim claim = GriefDefenderPlugin.getInstance().dataStore.getClaimAtPlayer(playerData, player.getLocation()); + // health regen + if (world.getProperties().getTotalTime() % 100 == 0L) { + final GameMode gameMode = player.get(Keys.GAME_MODE).get(); + // Handle player health regen + if (gameMode != GameModes.CREATIVE && gameMode != GameModes.SPECTATOR) { + final double maxHealth = player.get(Keys.MAX_HEALTH).get(); + final double currentHealth = player.get(Keys.HEALTH).get(); + if (currentHealth < maxHealth) { + final double regenAmount = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), playerData.getSubject(), Options.PLAYER_HEALTH_REGEN, claim); + if (regenAmount > 0) { + final double newHealth = currentHealth + regenAmount; + if (newHealth > maxHealth) { + player.offer(Keys.MAX_HEALTH, maxHealth); + } else { + player.offer(Keys.HEALTH, newHealth); + } + } + } + } + } + // teleport delay + if (world.getProperties().getTotalTime() % 20 == 0L) { + if (playerData.teleportDelay > 0) { + final int delay = playerData.teleportDelay - 1; + if (delay == 0) { + player.setLocation(playerData.teleportLocation); + playerData.teleportDelay = 0; + playerData.teleportLocation = null; + playerData.teleportSourceLocation = null; + continue; + } + TextAdapter.sendComponent(player, MessageStorage.MESSAGE_DATA.getMessage(MessageStorage.TELEPORT_DELAY_NOTICE, + ImmutableMap.of("delay", TextComponent.of(delay, TextColor.GOLD)))); + playerData.teleportDelay = delay; + } + } + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/task/TaxApplyTask.java b/sponge/src/main/java/com/griefdefender/task/TaxApplyTask.java new file mode 100644 index 0000000..3b2632f --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/task/TaxApplyTask.java @@ -0,0 +1,193 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.task; + +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.GriefDefender; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.economy.BankTransactionType; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimManager; +import com.griefdefender.configuration.GriefDefenderConfig; +import com.griefdefender.economy.GDBankTransaction; +import com.griefdefender.event.GDTaxClaimEvent; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.service.economy.EconomyService; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.world.storage.WorldProperties; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Set; + +public class TaxApplyTask implements Runnable { + + final WorldProperties worldProperties; + final EconomyService economyService; + final GriefDefenderConfig<?> activeConfig; + private int bankTransactionLogLimit = 60; + + public TaxApplyTask(WorldProperties worldProperties) { + this.worldProperties = worldProperties; + this.economyService = GriefDefenderPlugin.getInstance().economyService.get(); + this.activeConfig = GriefDefenderPlugin.getActiveConfig(this.worldProperties); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public void run() { + // don't do anything when there are no claims + GDClaimManager claimManager = GriefDefenderPlugin.getInstance().dataStore.getClaimWorldManager(this.worldProperties.getUniqueId()); + ArrayList<Claim> claimList = (ArrayList<Claim>) new ArrayList<>(claimManager.getWorldClaims()); + if (claimList.size() == 0) { + return; + } + + this.bankTransactionLogLimit = this.activeConfig.getConfig().claim.bankTransactionLogLimit; + Iterator<GDClaim> iterator = ((ArrayList) claimList.clone()).iterator(); + while (iterator.hasNext()) { + GDClaim claim = iterator.next(); + final GDPlayerData playerData = claim.getOwnerPlayerData(); + if (claim.isWilderness()) { + continue; + } + if (playerData == null) { + continue; + } + + if (!playerData.dataInitialized) { + continue; + } + + if (claim.isAdminClaim()) { + // search for town + final Set<Claim> children = claim.getChildren(false); + for (Claim child : children) { + if (child.isTown()) { + handleTownTax((GDClaim) child, playerData); + } else if (child.isBasicClaim()) { + handleClaimTax((GDClaim) child, playerData, false); + } + } + } else { + if (claim.isTown()) { + handleTownTax(claim, playerData); + } else if (claim.isBasicClaim()){ + handleClaimTax(claim, playerData, false); + } + } + } + } + + private void handleClaimTax(GDClaim claim, GDPlayerData playerData, boolean inTown) { + final GDPermissionUser user = playerData.getSubject(); + final Account claimAccount = null; //claim.getEconomyAccount().orElse(null); + if (claimAccount == null) { + return; + } + double taxRate = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), user, Options.TAX_RATE, claim); + double taxOwed = claim.getEconomyData().getTaxBalance() + (claim.getClaimBlocks() * taxRate); + try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) { + Sponge.getCauseStackManager().pushCause(GriefDefenderPlugin.getInstance()); + GDTaxClaimEvent event = new GDTaxClaimEvent(claim, taxRate, taxOwed); + GriefDefender.getEventManager().post(event); + if (event.cancelled()) { + return; + } + final double taxBalance = claim.getEconomyData().getTaxBalance(); + taxRate = event.getTaxRate(); + taxOwed = taxBalance + (claim.getClaimBlocks() * taxRate); + TransactionResult result = claimAccount.withdraw(this.economyService.getDefaultCurrency(), BigDecimal.valueOf(taxOwed), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() != ResultType.SUCCESS) { + final Instant localNow = Instant.now(); + Instant taxPastDueDate = claim.getEconomyData().getTaxPastDueDate().orElse(null); + if (taxPastDueDate == null) { + claim.getEconomyData().setTaxPastDueDate(Instant.now()); + } else { + final int taxExpirationDays = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Integer.class), user, Options.TAX_EXPIRATION, claim); + if (taxExpirationDays > 0) { + claim.getInternalClaimData().setExpired(true); + if (taxExpirationDays == 0) { + claim.getInternalClaimData().setExpired(true); + claim.getData().save(); + } else if (taxPastDueDate.plus(Duration.ofDays(taxExpirationDays)).isBefore(localNow)) { + claim.getInternalClaimData().setExpired(true); + claim.getData().save(); + } + } + } + final double totalTaxOwed = taxBalance + taxOwed; + claim.getEconomyData().setTaxBalance(totalTaxOwed); + claim.getEconomyData().addBankTransaction(new GDBankTransaction(BankTransactionType.TAX_FAIL, Instant.now(), taxOwed)); + } else { + claim.getEconomyData().addBankTransaction(new GDBankTransaction(BankTransactionType.TAX_SUCCESS, Instant.now(), taxOwed)); + claim.getEconomyData().setTaxPastDueDate(null); + claim.getEconomyData().setTaxBalance(0); + claim.getInternalClaimData().setExpired(false); + + if (inTown) { + final GDClaim town = claim.getTownClaim(); + town.getData() + .getEconomyData() + .addBankTransaction(new GDBankTransaction(BankTransactionType.TAX_SUCCESS, Instant.now(), taxOwed)); + //town.getEconomyAccount() + // .get() + // .deposit(this.economyService.getDefaultCurrency(), BigDecimal.valueOf(taxOwed), Sponge.getCauseStackManager().getCurrentCause()); + } + claim.getData().save(); + } + } + } + + private void handleTownTax(GDClaim town, GDPlayerData playerData) { + Account townAccount = null;//town.getEconomyAccount().orElse(null); + if (townAccount == null) { + // Virtual Accounts not supported by Economy Plugin so ignore + return; + } + Set<Claim> children = town.getChildren(true); + for (Claim child : children) { + // resident tax + if (child.isBasicClaim()) { + handleClaimTax((GDClaim) child, playerData, true); + } + } + if (town.getOwnerUniqueId().equals(playerData.playerID)) { + handleClaimTax(town, playerData, false); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/text/action/GDCallbackHolder.java b/sponge/src/main/java/com/griefdefender/text/action/GDCallbackHolder.java new file mode 100644 index 0000000..8e16d68 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/text/action/GDCallbackHolder.java @@ -0,0 +1,83 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.text.action; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; +import org.spongepowered.api.command.CommandSource; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +public class GDCallbackHolder { + public static final String CALLBACK_COMMAND = "callback"; + public static final String CALLBACK_COMMAND_QUALIFIED = "/gd:" + CALLBACK_COMMAND; + private static final GDCallbackHolder INSTANCE = new GDCallbackHolder(); + + static final ConcurrentMap<UUID, Consumer<CommandSource>> reverseMap = new ConcurrentHashMap<>(); + private static final LoadingCache<Consumer<CommandSource>, UUID> callbackCache = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES) + .removalListener(new RemovalListener<Consumer<CommandSource>, UUID>() { + @Override + public void onRemoval(RemovalNotification<Consumer<CommandSource>, UUID> notification) { + reverseMap.remove(notification.getValue(), notification.getKey()); + } + }) + .build(new CacheLoader<Consumer<CommandSource>, UUID>() { + @Override + public UUID load(Consumer<CommandSource> key) throws Exception { + UUID ret = UUID.randomUUID(); + reverseMap.putIfAbsent(ret, key); + return ret; + } + }); + + + public static GDCallbackHolder getInstance() { + return INSTANCE; + } + + + public UUID getOrCreateIdForCallback(Consumer<CommandSource> callback) { + return callbackCache.getUnchecked(checkNotNull(callback, "callback")); + } + + public Optional<Consumer<CommandSource>> getCallbackForUUID(UUID id) { + return Optional.of(reverseMap.get(id)); + } + + public String createCallbackRunCommand(Consumer<CommandSource> consumer) { + UUID callbackId = getOrCreateIdForCallback(consumer); + return CALLBACK_COMMAND_QUALIFIED + " " + callbackId; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/BlockPosCache.java b/sponge/src/main/java/com/griefdefender/util/BlockPosCache.java new file mode 100644 index 0000000..eaae7d5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/BlockPosCache.java @@ -0,0 +1,61 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.api.Tristate; +import org.spongepowered.common.SpongeImpl; + +public class BlockPosCache { + + private int lastTickCounter; + private short lastBlockPos; + private Tristate lastResult = Tristate.UNDEFINED; + + public BlockPosCache(short pos) { + this.lastBlockPos = pos; + this.lastTickCounter = SpongeImpl.getServer().getTickCounter(); + } + + public void setLastResult(Tristate result) { + this.lastResult = result; + } + + public Tristate getCacheResult(short pos) { + int currentTick = SpongeImpl.getServer().getTickCounter(); + if (this.lastBlockPos != pos) { + this.lastBlockPos = pos; + this.lastTickCounter = currentTick; + return Tristate.UNDEFINED; + } + + if ((currentTick - this.lastTickCounter) <= 2) { + this.lastTickCounter = currentTick; + return this.lastResult; + } + + this.lastTickCounter = currentTick; + return Tristate.UNDEFINED; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/BootstrapUtil.java b/sponge/src/main/java/com/griefdefender/util/BootstrapUtil.java new file mode 100644 index 0000000..298bc0e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/BootstrapUtil.java @@ -0,0 +1,65 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.GDBootstrap; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +public class BootstrapUtil { + + private final static Method METHOD_ADD_URL; + + static { + Method methodAddUrl = null; + try { + methodAddUrl = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + methodAddUrl.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + METHOD_ADD_URL = methodAddUrl; + } + + public static void addUrlToClassLoader(String name, File input) { + try { + final ClassLoader classLoader = GDBootstrap.class.getClassLoader(); + if (classLoader instanceof URLClassLoader) { + try { + METHOD_ADD_URL.invoke(classLoader, new URL("jar:file:" + input.getPath() + "!/")); + } catch (Throwable t) { + t.printStackTrace(); + } + } else { + throw new RuntimeException("Unknown classloader: " + classLoader.getClass()); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/CauseContextHelper.java b/sponge/src/main/java/com/griefdefender/util/CauseContextHelper.java new file mode 100644 index 0000000..fabc10b --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/CauseContextHelper.java @@ -0,0 +1,256 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimContexts; +import com.griefdefender.api.permission.Context; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.internal.util.NMSUtil; +import com.griefdefender.permission.GDPermissions; +import net.kyori.text.TextComponent; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.block.tileentity.TileEntity; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.Entity; +import org.spongepowered.api.entity.living.Living; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.event.Event; +import org.spongepowered.api.event.cause.Cause; +import org.spongepowered.api.event.cause.EventContext; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.event.world.ExplosionEvent; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.world.storage.WorldProperties; +import org.spongepowered.common.SpongeImplHooks; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CauseContextHelper { + + public static User getEventUser(Event event) { + final Cause cause = event.getCause(); + final EventContext context = event.getContext(); + // Don't attempt to set user for leaf decay + if (context.containsKey(EventContextKeys.LEAVES_DECAY)) { + return null; + } + + User user = null; + User fakePlayer = null; + if (cause != null) { + user = cause.first(User.class).orElse(null); + if (user != null && user instanceof Entity && NMSUtil.getInstance().isFakePlayer((Entity) user)) { + fakePlayer = user; + } + } + + // Only check notifier for fire spread + if (context.containsKey(EventContextKeys.FIRE_SPREAD)) { + return context.get(EventContextKeys.NOTIFIER).orElse(null); + } + + if (user == null || fakePlayer != null) { + // Always use owner for ticking TE's + // See issue MinecraftPortCentral/GriefDefender#610 for more information + if (cause.containsType(TileEntity.class)) { + user = context.get(EventContextKeys.OWNER) + .orElse(context.get(EventContextKeys.NOTIFIER) + .orElse(context.get(EventContextKeys.CREATOR) + .orElse(null))); + } else { + user = context.get(EventContextKeys.NOTIFIER) + .orElse(context.get(EventContextKeys.OWNER) + .orElse(context.get(EventContextKeys.CREATOR) + .orElse(null))); + } + } + + if (user == null) { + // fall back to fakeplayer if we still don't have a user + user = fakePlayer; + if (event instanceof ExplosionEvent) { + // Check igniter + final Living living = context.get(EventContextKeys.IGNITER).orElse(null); + if (living != null && living instanceof User) { + user = (User) living; + } + } + } + + return user; + } + + // Credit to digitok of freenode for the regex assistance + //final String CONTEXT_PATTERN2 = "^contexts?\\[ *(?:[\\w.-]+:[\\w.-]+:[\\w\\/.-]+ *(?:, *(?!\\]$)|(?=\\]$)))+ *\\]$"; + //private static final Pattern CONTEXT_PATTERN = Pattern.compile("^context?\\[ *(?:[\\w.-]+:[\\w.-]+:[\\w\\/.-]+ *(?:, *(?!\\]$)|(?=\\]$)))+ *\\]$"); + + // original = private static final Pattern CONTEXT_PATTERN = Pattern.compile("^context?\\[ *((?:[\\w.-]+:[\\w.-]+(?::[\\w\\/.-]+)? *(?:, *(?!\\]$)|(?=\\]$)))+) *\\]$"); + private static final Pattern CONTEXT_PATTERN = Pattern.compile("^context?\\[ *((?:[\\w.-]+=[\\w.-]+(?::[\\w\\/.-]+)? *(?:, *(?!\\]$)|(?=\\]$)))+) *\\]$"); + private static final Pattern CONTEXT_SPLIT = Pattern.compile("^context?\\[ *((?:[\\w.-]+:[\\w.-]+:[\\w\\/.-]+(?: *, *(?!\\]$)|(?= *\\]$)))+) *\\]$"); + private static final List<String> VALID_CONTEXTS = Arrays.asList("world", "server", "mode", "player", "group", "source", "used_item", "type"); + // final String regex = "^context?\\[ *((?:[\\w.-]+:[\\w.-]+:[\\w\\/.-]+(?: *, *(?!\\]$)|(?= *\\]$)))+) *\\]$"; + public static Set<Context> generateContexts(CommandSource src, Claim claim, String context) { + return generateContexts(src, claim, context, false); + } + + public static Set<Context> generateContexts(CommandSource src, Claim claim, String context, boolean isOption) { + // verify context is valid + if (context == null) { + return new HashSet<>(); + } + context = context.replace(" ", ""); + Matcher matcher = CONTEXT_PATTERN.matcher(context); + if (!matcher.find()) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("Invalid context entered.")); + return null; + } + /*if (context.contains("mode=") && !context.contains("type=")) { + GriefDefenderPlugin.sendMessage(src, Text.of("Context 'mode' requires 'type'.")); + return null; + }*/ + + final boolean canManageDefaults = src.hasPermission(GDPermissions.MANAGE_FLAG_DEFAULTS); + final boolean canManageOverrides = src.hasPermission(GDPermissions.MANAGE_FLAG_OVERRIDES); + final Set<Context> contextSet = new HashSet<>(); + final String contexts = matcher.group(1); + String[] split = contexts.split(","); + String reason = null; + for (int i = 0; i < split.length; i++) { + String[] parts = split[i].split("="); + //final String[] parts = split[i].split(":"); + if (parts.length < 2) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("Invalid context entered.")); + return null; + } + final String contextName = parts[0]; + parts = parts[1].split(":"); + if (parts.length < 1) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("Invalid context entered.")); + return null; + } + final String arg1 = parts[0]; + final String arg2 = parts.length > 1 ? parts[1] : null; + String id = ""; + if (arg2 == null) { + id = "minecraft:" + arg1; + } else { + id = arg1 + ":" + arg2; + } + if (contextName.equals("world")) { + boolean found = false; + for (WorldProperties worldProperties : Sponge.getServer().getAllWorldProperties()) { + if (arg1.equalsIgnoreCase(worldProperties.getWorldName())) { + contextSet.add(new Context(contextName, arg1)); + found = true; + break; + } + } + if (!found) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("No world found with name '" + arg1 + "'.")); + return null; + } + } else if (contextName.equals("server")) { + contextSet.add(new Context(contextName, arg1)); + } else if (contextName.equals("default")) { + if (!canManageDefaults) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PERMISSION_FLAG_DEFAULTS); + return new HashSet<>(); + } + if (arg1.equals("any") || arg1.equals("global")) { + contextSet.add(ClaimContexts.GLOBAL_DEFAULT_CONTEXT); + } else if (arg1.equals("admin")) { + contextSet.add(ClaimContexts.ADMIN_DEFAULT_CONTEXT); + } else if (arg1.equals("basic")) { + contextSet.add(ClaimContexts.BASIC_DEFAULT_CONTEXT); + } else if (arg1.equals("town")) { + contextSet.add(ClaimContexts.TOWN_DEFAULT_CONTEXT); + } else if (arg1.equals("wilderness")) { + contextSet.add(ClaimContexts.WILDERNESS_DEFAULT_CONTEXT); + } else { + GriefDefenderPlugin.sendMessage(src, TextComponent.of(contextName + " context requires format '" + contextName + ":type'. \nValid types are 'world', 'server', and 'global'.")); + return null; + } + } else if (contextName.equals("override")) { + if (isOption) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("Options do not support overrides.")); + return null; + } + if (!canManageOverrides) { + GriefDefenderPlugin.sendMessage(src, MessageCache.getInstance().PERMISSION_FLAG_OVERRIDES); + return null; + } + if (arg1.equals("any") || arg1.equals("global")) { + contextSet.add(ClaimContexts.GLOBAL_OVERRIDE_CONTEXT); + } else if (arg1.equals("admin")) { + contextSet.add(ClaimContexts.ADMIN_OVERRIDE_CONTEXT); + } else if (arg1.equals("basic")) { + contextSet.add(ClaimContexts.BASIC_OVERRIDE_CONTEXT); + } else if (arg1.equals("town")) { + contextSet.add(ClaimContexts.TOWN_OVERRIDE_CONTEXT); + } else if (arg1.equals("wilderness")) { + contextSet.add(ClaimContexts.WILDERNESS_OVERRIDE_CONTEXT); + } else if (arg1.equals("claim")) { + contextSet.add(((GDClaim) claim).getOverrideClaimContext()); + } else { + GriefDefenderPlugin.sendMessage(src, TextComponent.of(contextName + " context requires format '" + contextName + ":type'. \nValid types are 'world', 'server', and 'global'.")); + return new HashSet<>(); + } + } else if (contextName.equals("player")) { + contextSet.add(new Context(contextName, arg1)); + } else if (contextName.equals("group")) { + contextSet.add(new Context(contextName, arg1)); + } else if (contextName.equals("meta")) { + contextSet.add(new Context(contextName, arg1)); + } else if (contextName.equals("source")) { + contextSet.add(new Context(contextName, id)); + } else if (contextName.contentEquals("state")) { + contextSet.add(new Context(contextName, id)); + } else if (contextName.equals("used_item")) { + final ItemType type = Sponge.getRegistry().getType(ItemType.class, id).orElse(null); + if (type == null) { + GriefDefenderPlugin.sendMessage(src, TextComponent.of("Invalid context entered.")); + return null; + } + contextSet.add(new Context(contextName, type.getId())); + } else { + if (arg2 == null) { + contextSet.add(new Context(contextName, arg1)); + } else { + contextSet.add(new Context(contextName, id)); + } + } + } + + return contextSet; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/ClaimClickData.java b/sponge/src/main/java/com/griefdefender/util/ClaimClickData.java new file mode 100644 index 0000000..f79db23 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/ClaimClickData.java @@ -0,0 +1,37 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.claim.GDClaim; + +public class ClaimClickData { + public final GDClaim claim; + public final Object value; + + public ClaimClickData(GDClaim claim, Object value) { + this.claim = claim; + this.value = value; + } +} \ No newline at end of file diff --git a/sponge/src/main/java/com/griefdefender/util/EconomyUtil.java b/sponge/src/main/java/com/griefdefender/util/EconomyUtil.java new file mode 100644 index 0000000..7b3b85a --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/EconomyUtil.java @@ -0,0 +1,213 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.flowpowered.math.vector.Vector3i; +import com.google.common.collect.ImmutableMap; +import com.google.common.reflect.TypeToken; +import com.griefdefender.GDBootstrap; +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.claim.ClaimResult; +import com.griefdefender.api.claim.ClaimResultType; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.permission.option.Options; +import com.griefdefender.cache.MessageCache; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.claim.GDClaimResult; +import com.griefdefender.command.CommandHelper; +import com.griefdefender.configuration.MessageStorage; +import com.griefdefender.event.GDCauseStackManager; +import com.griefdefender.internal.provider.WorldEditProvider; +import com.griefdefender.internal.util.BlockUtil; +import com.griefdefender.permission.GDPermissionManager; +import com.griefdefender.permission.GDPermissionUser; +import com.griefdefender.text.action.GDCallbackHolder; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.event.ClickEvent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.command.CommandSource; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.CauseStackManager; +import org.spongepowered.api.event.cause.EventContextKeys; +import org.spongepowered.api.service.economy.Currency; +import org.spongepowered.api.service.economy.account.Account; +import org.spongepowered.api.service.economy.transaction.ResultType; +import org.spongepowered.api.service.economy.transaction.TransactionResult; +import org.spongepowered.api.world.World; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +public class EconomyUtil { + + private static EconomyUtil instance; + + public static EconomyUtil getInstance() { + return instance; + } + + static { + instance = new EconomyUtil(); + } + + public void economyCreateClaimConfirmation(Player player, GDPlayerData playerData, int height, Vector3i point1, Vector3i point2, ClaimType claimType, boolean cuboid, Claim parent) { + GDClaim claim = new GDClaim(player.getWorld(), point1, point2, claimType, player.getUniqueId(), cuboid); + claim.parent = (GDClaim) parent; + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(player); + final int claimCost = BlockUtil.getInstance().getClaimBlockCost(player.getWorld(), claim.lesserBoundaryCorner, claim.greaterBoundaryCorner, claim.cuboid); + final Double economyBlockCost = GDPermissionManager.getInstance().getInternalOptionValue(TypeToken.of(Double.class), user, Options.ECONOMY_BLOCK_COST, claim); + final double requiredFunds = claimCost * economyBlockCost; + final Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_CONFIRMATION, + ImmutableMap.of("amount", String.valueOf("$" + requiredFunds))); + final Component buyConfirmationText = TextComponent.builder() + .append(message) + .append(TextComponent.builder() + .append("\n[") + .append(MessageCache.getInstance().LABEL_CONFIRM.color(TextColor.GREEN)) + .append("]\n") + .clickEvent(ClickEvent.runCommand(GDCallbackHolder.getInstance().createCallbackRunCommand(economyClaimBuyConfirmed(player, playerData, height, requiredFunds, point1, point2, claimType, cuboid, parent)))).build()) + .build(); + GriefDefenderPlugin.sendMessage(player, buyConfirmationText); + } + + private static Consumer<CommandSource> economyClaimBuyConfirmed(Player player, GDPlayerData playerData, int height, double requiredFunds, Vector3i lesserBoundaryCorner, Vector3i greaterBoundaryCorner, ClaimType claimType, boolean cuboid, Claim parent) { + return confirm -> { + // try to create a new claim + ClaimResult result = null; + GDCauseStackManager.getInstance().pushCause(player); + result = GriefDefenderPlugin.getInstance().dataStore.createClaim( + player.getWorld(), + lesserBoundaryCorner, + greaterBoundaryCorner, + claimType, player.getUniqueId(), cuboid); + GDCauseStackManager.getInstance().popCause(); + + GDClaim gdClaim = (GDClaim) result.getClaim().orElse(null); + // if it didn't succeed, tell the player why + if (!result.successful()) { + if (result.getResultType() == ClaimResultType.OVERLAPPING_CLAIM) { + GDClaim overlapClaim = (GDClaim) result.getClaim().get(); + GriefDefenderPlugin.sendMessage(player, MessageCache.getInstance().CREATE_OVERLAP_SHORT); + Set<Claim> claims = new HashSet<>(); + claims.add(overlapClaim); + CommandHelper.showClaims(player, claims, height, true); + } else { + GriefDefenderPlugin.sendMessage(player, GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_FAILED_RESULT, + ImmutableMap.of("reason", result.getResultType()))); + } + return; + } + + // otherwise, advise him on the /trust command and show him his new claim + else { + Component message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_CLAIM_BUY_CONFIRMED, ImmutableMap.of( + "amount", requiredFunds)); + GriefDefenderPlugin.sendMessage(player, message); + playerData.lastShovelLocation = null; + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.CREATE_SUCCESS, ImmutableMap.of( + "type", gdClaim.getFriendlyNameType(true))); + GriefDefenderPlugin.sendMessage(player, message); + final WorldEditProvider worldEditProvider = GriefDefenderPlugin.getInstance().worldEditProvider; + if (worldEditProvider != null) { + worldEditProvider.stopVisualDrag(player); + worldEditProvider.visualizeClaim(gdClaim, player, playerData, false); + } + gdClaim.getVisualizer().createClaimBlockVisuals(height, player.getLocation(), playerData); + gdClaim.getVisualizer().apply(player, false); + } + }; + } + + public static TransactionResult depositFunds(UUID uuid, double amount) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(uuid).orElse(null); + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + return playerAccount.deposit(defaultCurrency, BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause()); + } + + public static TransactionResult withdrawFunds(UUID uuid, double amount) { + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(uuid).orElse(null); + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + return playerAccount.withdraw(defaultCurrency, BigDecimal.valueOf(amount), Sponge.getCauseStackManager().getCurrentCause()); + } + + public GDClaimResult checkEconomyFunds(GDClaim claim, GDPlayerData newPlayerData, boolean withdrawFunds) { + if (!GriefDefenderPlugin.getInstance().isEconomyModeEnabled()) { + return new GDClaimResult(claim, ClaimResultType.ECONOMY_ACCOUNT_NOT_FOUND); + } + + final Object root = GDCauseStackManager.getInstance().getCurrentCause().root(); + final Player player = root instanceof Player ? (Player) root : null; + final World world = claim.getWorld(); + final int claimCost = BlockUtil.getInstance().getClaimBlockCost(world, claim.lesserBoundaryCorner, claim.greaterBoundaryCorner, claim.cuboid); + final GDPermissionUser targetPlayer = newPlayerData.getSubject(); + final Account playerAccount = GriefDefenderPlugin.getInstance().economyService.get().getOrCreateAccount(targetPlayer.getUniqueId()).orElse(null); + if (playerAccount == null) { + return new GDClaimResult(claim, ClaimResultType.ECONOMY_ACCOUNT_NOT_FOUND); + } + + final Currency defaultCurrency = GriefDefenderPlugin.getInstance().economyService.get().getDefaultCurrency(); + double requiredFunds = claimCost * claim.getOwnerEconomyBlockCost(); + final BigDecimal currentFunds = playerAccount.getBalance(defaultCurrency); + if (currentFunds.doubleValue() < requiredFunds) { + Component message = null; + if (player != null) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_NOT_ENOUGH_FUNDS, ImmutableMap.of( + "balance", String.valueOf("$" + currentFunds.doubleValue()), + "amount", String.valueOf("$" + requiredFunds))); + GriefDefenderPlugin.sendMessage(player, message); + } + + //playerData.lastShovelLocation = null; + // playerData.claimResizing = null; + return new GDClaimResult(claim, ClaimResultType.ECONOMY_NOT_ENOUGH_FUNDS, message); + } + + if (withdrawFunds) { + final TransactionResult result = playerAccount.withdraw(defaultCurrency, BigDecimal.valueOf(requiredFunds), Sponge.getCauseStackManager().getCurrentCause()); + if (result.getResult() != ResultType.SUCCESS) { + Component message = null; + if (player != null) { + message = GriefDefenderPlugin.getInstance().messageData.getMessage(MessageStorage.ECONOMY_WITHDRAW_ERROR, ImmutableMap.of( + "reason", result.getResult().name())); + GriefDefenderPlugin.sendMessage(player, message); + } + + //playerData.lastShovelLocation = null; + //playerData.claimResizing = null; + return new GDClaimResult(claim, ClaimResultType.ECONOMY_WITHDRAW_FAIL, message); + } + } + + return new GDClaimResult(claim, ClaimResultType.SUCCESS); + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/EntityUtils.java b/sponge/src/main/java/com/griefdefender/util/EntityUtils.java new file mode 100644 index 0000000..7824b28 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/EntityUtils.java @@ -0,0 +1,72 @@ +/* + * This file is part of Sponge, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered <https://www.spongepowered.org> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import net.minecraft.entity.IEntityOwnable; +import net.minecraft.entity.player.EntityPlayer; +import org.spongepowered.api.entity.Entity; + +import java.util.UUID; + +import javax.annotation.Nullable; + +public class EntityUtils { + + public static UUID getOwnerUniqueId(Entity entity) { + if (entity instanceof EntityPlayer) { + return null; + } + + UUID ownerUniqueId = entity.getCreator().orElse(null); + if (ownerUniqueId == null && entity instanceof IEntityOwnable) { + IEntityOwnable ownable = (IEntityOwnable) entity; + ownerUniqueId = ownable.getOwnerId(); + } + + return ownerUniqueId; + } + + @Nullable + public static Entity getControllingPassenger(Entity entity) { + return entity.getPassengers().isEmpty() ? null :entity.getPassengers().get(0); + } + + public static String getFriendlyName(net.minecraft.entity.Entity mcEntity) { + String entityName = mcEntity.getName(); + final String[] parts = entityName.split(":"); + if (parts.length > 1) { + entityName = parts[1]; + } + if (entityName.contains(".")) { + if ((entityName.indexOf(".") + 1) < entityName.length()) { + entityName = entityName.substring(entityName.indexOf(".") + 1, entityName.length()); + } + } + + entityName = entityName.replace("entity", ""); + entityName = entityName.replaceAll("[^A-Za-z0-9]", ""); + return entityName; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/HttpClient.java b/sponge/src/main/java/com/griefdefender/util/HttpClient.java new file mode 100644 index 0000000..dcce195 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/HttpClient.java @@ -0,0 +1,107 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.griefdefender.util; + +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +import java.io.IOException; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.Collections; +import java.util.List; + +public class HttpClient { + private static OkHttpClient client = null; + + private static synchronized OkHttpClient getClient() { + if (client == null) { + client = new OkHttpClient.Builder() + .proxySelector(new NullSafeProxySelector()) + .addInterceptor(new UserAgentInterceptor()) + .build(); + } + return client; + } + + public static Response makeCall(Request request) throws IOException { + Response response = getClient().newCall(request).execute(); + if (!response.isSuccessful()) { + throw exceptionForUnsuccessfulResponse(response); + } + return response; + } + + private static RuntimeException exceptionForUnsuccessfulResponse(Response response) { + String msg = ""; + try (ResponseBody responseBody = response.body()) { + if (responseBody != null) { + msg = responseBody.string(); + } + } catch (IOException e) { + // ignore + } + return new RuntimeException("Got response: " + response.code() + " - " + response.message() + " - " + msg); + } + + private static final class UserAgentInterceptor implements Interceptor { + @Override + public Response intercept(Chain chain) throws IOException { + Request orig = chain.request(); + Request modified = orig.newBuilder() + .header("User-Agent", "griefdefender") + .build(); + + return chain.proceed(modified); + } + } + + // sometimes ProxySelector#getDefault returns null, and okhttp doesn't like that + private static final class NullSafeProxySelector extends ProxySelector { + private static final List<Proxy> DIRECT = Collections.singletonList(Proxy.NO_PROXY); + + @Override + public List<Proxy> select(URI uri) { + ProxySelector def = ProxySelector.getDefault(); + if (def == null) { + return DIRECT; + } + return def.select(uri); + } + + @Override + public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { + ProxySelector def = ProxySelector.getDefault(); + if (def != null) { + def.connectFailed(uri, sa, ioe); + } + } + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/PaginationUtil.java b/sponge/src/main/java/com/griefdefender/util/PaginationUtil.java new file mode 100644 index 0000000..5d29558 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/PaginationUtil.java @@ -0,0 +1,93 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +// A bad hack at attempting to track active pages +public class PaginationUtil { + + private static PaginationUtil instance; + + public static PaginationUtil getInstance() { + return instance; + } + + static { + instance = new PaginationUtil(); + } + + // The active page resets when a new command is entered by same player + private final Map<UUID, Integer> activePageMap = new HashMap<>(); + + public void updateActiveCommand(UUID uuid, String command, String args) { + if (command.equalsIgnoreCase("callback") || command.equalsIgnoreCase("gpreload")) { + // ignore + return; + } + + if (command.equalsIgnoreCase("page")) { + final Integer activePage = this.activePageMap.get(uuid); + if (activePage != null) { + try { + final Integer page = Integer.parseInt(args); + this.activePageMap.put(uuid, page); + } catch (Throwable t) { + + } + } + return; + } + + if (command.equalsIgnoreCase("pagination")) { + final Integer activePage = this.activePageMap.get(uuid); + if (activePage != null) { + final boolean isNext = args.contains("next"); + if (isNext) { + this.activePageMap.put(uuid, activePage + 1); + } else if (activePage != 1) { + this.activePageMap.put(uuid, activePage - 1); + } + } + return; + } + + resetActivePage(uuid); + } + + public void resetActivePage(UUID uuid) { + this.activePageMap.put(uuid, 1); + } + + public Integer getActivePage(UUID uuid) { + return this.activePageMap.get(uuid); + } + + public void removeActivePageData(UUID uuid) { + this.activePageMap.remove(uuid); + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/PermissionUtil.java b/sponge/src/main/java/com/griefdefender/util/PermissionUtil.java new file mode 100644 index 0000000..c8612e5 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/PermissionUtil.java @@ -0,0 +1,242 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.Tristate; +import com.griefdefender.api.claim.Claim; +import com.griefdefender.api.permission.Context; +import com.griefdefender.api.permission.PermissionResult; +import com.griefdefender.api.permission.flag.Flag; +import com.griefdefender.api.permission.option.Option; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.claim.GDClaim; +import com.griefdefender.permission.GDPermissionHolder; +import com.griefdefender.provider.PermissionProvider; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class PermissionUtil { + + private final PermissionProvider PERMISSION_PROVIDER; + + private static PermissionUtil instance; + + public static PermissionUtil getInstance() { + return instance; + } + + static { + instance = new PermissionUtil(); + } + + public PermissionUtil() { + this.PERMISSION_PROVIDER = GriefDefenderPlugin.getInstance().getPermissionProvider(); + } + + public boolean hasGroupSubject(String identifier) { + return PERMISSION_PROVIDER.hasGroupSubject(identifier); + } + + public UUID lookupUserUniqueId(String name) { + return PERMISSION_PROVIDER.lookupUserUniqueId(name); + } + + public List<String> getAllLoadedPlayerNames() { + return PERMISSION_PROVIDER.getAllLoadedPlayerNames(); + } + + public List<String> getAllLoadedGroupNames() { + return PERMISSION_PROVIDER.getAllLoadedGroupNames(); + } + + public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder) { + PERMISSION_PROVIDER.addActiveContexts(contexts, permissionHolder); + } + + public void addActiveContexts(Set<Context> contexts, GDPermissionHolder permissionHolder, GDPlayerData playerData, Claim claim) { + PERMISSION_PROVIDER.addActiveContexts(contexts, permissionHolder, null, null); + } + + public boolean containsDefaultContext(Set<Context> contexts) { + for (Context context : contexts) { + if (context.getKey().equals("gd_claim_default")) { + return true; + } + } + + return false; + } + + public boolean containsOverrideContext(Set<Context> contexts) { + for (Context context : contexts) { + if (context.getKey().equals("gd_claim_override")) { + return true; + } + } + + return false; + } + + public void clearPermissions(GDClaim claim) { + PERMISSION_PROVIDER.clearPermissions(claim); + } + + public void clearPermissions(GDPermissionHolder holder, Context context) { + PERMISSION_PROVIDER.clearPermissions(holder, context); + } + + public void clearPermissions(GDPermissionHolder holder, Set<Context> contexts) { + PERMISSION_PROVIDER.clearPermissions(holder, contexts); + } + + public boolean holderHasPermission(GDPermissionHolder holder, String permission) { + return PERMISSION_PROVIDER.holderHasPermission(holder, permission); + } + + public Map<String, Boolean> getPermissions(GDPermissionHolder holder, Set<Context> contexts) { + return PERMISSION_PROVIDER.getPermissions(holder, contexts); + } + + public Map<String, String> getOptions(GDPermissionHolder holder, Set<Context> contexts) { + return PERMISSION_PROVIDER.getOptions(holder, contexts); + } + + public Map<Set<Context>, Map<String, Boolean>> getPermanentPermissions(GDPermissionHolder holder) { + return PERMISSION_PROVIDER.getPermanentPermissions(holder); + } + + public Map<Set<Context>, Map<String, Boolean>> getTransientPermissions(GDPermissionHolder holder) { + return PERMISSION_PROVIDER.getTransientPermissions(holder); + } + + public Map<Set<Context>, Map<String, String>> getPermanentOptions(GDPermissionHolder holder) { + return PERMISSION_PROVIDER.getPermanentOptions(holder); + } + + public Map<Set<Context>, Map<String, String>> getTransientOptions(GDPermissionHolder holder) { + return PERMISSION_PROVIDER.getTransientOptions(holder); + } + + public Map<String, String> getPermanentOptions(GDPermissionHolder holder, Set<Context> contexts) { + return PERMISSION_PROVIDER.getPermanentOptions(holder, contexts); + } + + public Map<String, String> getTransientOptions(GDPermissionHolder holder, Set<Context> contexts) { + return PERMISSION_PROVIDER.getTransientOptions(holder, contexts); + } + + public Map<Set<Context>, Map<String, Boolean>> getAllPermissions(GDPermissionHolder holder) { + return PERMISSION_PROVIDER.getAllPermissions(holder); + } + + public Tristate getPermissionValue(GDPermissionHolder holder, String permission) { + return PERMISSION_PROVIDER.getPermissionValue(holder, permission); + } + + public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts) { + return PERMISSION_PROVIDER.getPermissionValue(claim, holder, permission, contexts); + } + + public Tristate getPermissionValue(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, boolean checkTransient) { + return PERMISSION_PROVIDER.getPermissionValue(claim, holder, permission, contexts, checkTransient); + } + + public Tristate getPermissionValue(GDPermissionHolder holder, String permission, Set<Context> contexts) { + return PERMISSION_PROVIDER.getPermissionValue(holder, permission, contexts); + } + + public Tristate getPermissionValueWithRequiredContexts(GDClaim claim, GDPermissionHolder holder, String permission, Set<Context> contexts, String contextFilter) { + return PERMISSION_PROVIDER.getPermissionValueWithRequiredContexts(claim, holder, permission, contexts, contextFilter); + } + + public String getOptionValue(GDPermissionHolder holder, Option option, Set<Context> contexts) { + return PERMISSION_PROVIDER.getOptionValue(holder, option, contexts); + } + + public PermissionResult setOptionValue(GDPermissionHolder holder, String permission, String value, Set<Context> contexts) { + return PERMISSION_PROVIDER.setOptionValue(holder, permission, value, contexts); + } + + public PermissionResult setPermissionValue(GDPermissionHolder holder, Flag flag, Tristate value, Set<Context> contexts) { + return PERMISSION_PROVIDER.setPermissionValue(holder, flag, value, contexts); + } + + public boolean setPermissionValue(GDPermissionHolder holder, String permission, Tristate value, Set<Context> contexts) { + return PERMISSION_PROVIDER.setPermissionValue(holder, permission, value, contexts); + } + + public void setTransientOption(GDPermissionHolder holder, String permission, String value, Set<Context> contexts) { + PERMISSION_PROVIDER.setTransientOption(holder, permission, value, contexts); + } + + public void setTransientPermission(GDPermissionHolder holder, String permission, Boolean value, Set<Context> contexts) { + PERMISSION_PROVIDER.setTransientPermission(holder, permission, value, contexts); + } + + public void refreshCachedData(GDPermissionHolder holder) { + PERMISSION_PROVIDER.refreshCachedData(holder); + } + + public boolean containsKey(Set<Context> contexts, String key) { + for (Context context : contexts) { + if (context.getKey().equalsIgnoreCase(key)) { + return true; + } + } + return false; + } + + public Tristate getTristateFromString(String value) { + Tristate tristate = null; + int intValue = -999; + try { + intValue = Integer.parseInt(value); + if (intValue <= -1) { + tristate = Tristate.FALSE; + } else if (intValue == 0) { + tristate = Tristate.UNDEFINED; + } else { + tristate = Tristate.TRUE; + } + return tristate; + + } catch (NumberFormatException e) { + // ignore + } + + try { + tristate = Tristate.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + + return tristate; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/PlayerUtil.java b/sponge/src/main/java/com/griefdefender/util/PlayerUtil.java new file mode 100644 index 0000000..84056fc --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/PlayerUtil.java @@ -0,0 +1,207 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import com.griefdefender.GDPlayerData; +import com.griefdefender.GriefDefenderPlugin; +import com.griefdefender.api.claim.ClaimType; +import com.griefdefender.api.claim.ClaimTypes; +import com.griefdefender.api.claim.ShovelType; +import com.griefdefender.api.claim.ShovelTypes; +import com.griefdefender.api.permission.option.type.CreateModeTypes; +import com.griefdefender.cache.PermissionHolderCache; +import com.griefdefender.internal.visual.ClaimVisual; +import com.griefdefender.internal.visual.GDClaimVisualType; +import com.griefdefender.permission.GDPermissionUser; +import net.kyori.text.Component; +import net.kyori.text.TextComponent; +import net.kyori.text.format.TextColor; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.data.property.entity.EyeLocationProperty; +import org.spongepowered.api.data.type.HandTypes; +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.entity.living.player.User; +import org.spongepowered.api.item.ItemType; +import org.spongepowered.api.item.inventory.ItemStack; +import org.spongepowered.api.service.user.UserStorageService; +import org.spongepowered.api.util.Direction; + +import java.util.Optional; +import java.util.UUID; + +import javax.annotation.Nullable; + +public class PlayerUtil { + + private static Direction[] faces = { Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + + private static PlayerUtil instance; + + public static PlayerUtil getInstance() { + return instance; + } + + static { + instance = new PlayerUtil(); + } + + public Direction getBlockFace(Player player) { + return faces[Math.round((int) player.getTransform().getYaw() / 90f) & 0x3].getOpposite(); + } + + public Direction getBlockFace(String param) { + Direction face = null; + try { + face = Direction.valueOf(param.toUpperCase()); + } catch (IllegalArgumentException e) { + // ignore + } + return face; + } + + public boolean hasItemInOneHand(Player player, ItemType itemType) { + ItemStack mainHand = player.getItemInHand(HandTypes.MAIN_HAND).orElse(null); + ItemStack offHand = player.getItemInHand(HandTypes.OFF_HAND).orElse(null); + if ((mainHand != null && mainHand.getType().equals(itemType)) || (offHand != null && offHand.getType().equals(itemType))) { + return true; + } + + return false; + } + + public boolean hasItemInOneHand(Player player) { + ItemStack mainHand = player.getItemInHand(HandTypes.MAIN_HAND).orElse(null); + ItemStack offHand = player.getItemInHand(HandTypes.OFF_HAND).orElse(null); + if (mainHand != null || offHand != null) { + return true; + } + + return false; + } + + public ClaimType getClaimTypeFromShovel(ShovelType shovelMode) { + if (shovelMode == ShovelTypes.ADMIN) { + return ClaimTypes.ADMIN; + } + if (shovelMode == ShovelTypes.SUBDIVISION) { + return ClaimTypes.SUBDIVISION; + } + if (shovelMode == ShovelTypes.TOWN) { + return ClaimTypes.TOWN; + } + return ClaimTypes.BASIC; + } + + public Component getClaimTypeComponentFromShovel(ShovelType shovelMode) { + return getClaimTypeComponentFromShovel(shovelMode, true); + } + + public Component getClaimTypeComponentFromShovel(ShovelType shovelMode, boolean upper) { + if (shovelMode == ShovelTypes.ADMIN) { + if (upper) { + return TextComponent.of(ClaimTypes.ADMIN.getName().toUpperCase(), TextColor.RED); + } + return TextComponent.of("Admin", TextColor.RED); + } + + if (shovelMode == ShovelTypes.TOWN) { + if (upper) { + return TextComponent.of(ClaimTypes.TOWN.getName().toUpperCase(), TextColor.GREEN); + } + return TextComponent.of("Town", TextColor.GREEN); + } + + if (shovelMode == ShovelTypes.SUBDIVISION) { + if (upper) { + return TextComponent.of(ClaimTypes.SUBDIVISION.getName().toUpperCase(), TextColor.AQUA); + } + return TextComponent.of("Subdivision", TextColor.AQUA); + } + if (upper) { + return TextComponent.of(ClaimTypes.BASIC.getName().toUpperCase(), TextColor.YELLOW); + } + return TextComponent.of("Basic", TextColor.YELLOW); + } + + public GDClaimVisualType getVisualTypeFromShovel(ShovelType shovelMode) { + if (shovelMode == ShovelTypes.ADMIN) { + return ClaimVisual.ADMIN; + } + if (shovelMode == ShovelTypes.SUBDIVISION) { + return ClaimVisual.SUBDIVISION; + } + if (shovelMode == ShovelTypes.TOWN) { + return ClaimVisual.TOWN; + } + return ClaimVisual.BASIC; + } + + @Nullable + public String getUserName(UUID uuid) { + if (uuid.equals(GriefDefenderPlugin.PUBLIC_UUID)) { + return "public"; + } + if (uuid.equals(GriefDefenderPlugin.ADMIN_USER_UUID) || uuid.equals(GriefDefenderPlugin.WORLD_USER_UUID)) { + return "administrator"; + } + + final GDPermissionUser user = PermissionHolderCache.getInstance().getOrCreateUser(uuid); + if (user == null) { + return "unknown"; + } + + return user.getName(); + } + + public String getPlayerName(String uuid) { + if (uuid.equals(GriefDefenderPlugin.WORLD_USER_UUID.toString())) { + return "administrator"; + } + if (uuid.equals(GriefDefenderPlugin.ADMIN_USER_UUID.toString()) || uuid.equals(GriefDefenderPlugin.WORLD_USER_UUID.toString())) { + return "administrator"; + } + + Optional<User> user = Sponge.getGame().getServiceManager().provide(UserStorageService.class).get().get(UUID.fromString(uuid)); + if (!user.isPresent()) { + return "unknown"; + } + + return user.get().getName(); + } + + public int getEyeHeight(Player player) { + return player.getProperty(EyeLocationProperty.class).get().getValue().getFloorY(); + } + + public int getVisualClaimHeight(GDPlayerData playerData, int height) { + if (playerData.getClaimCreateMode() == CreateModeTypes.VOLUME) { + return height; + } + if (playerData.getMaxClaimLevel() < 255) { + return playerData.getMaxClaimLevel(); + } + return 0; + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/SpongeContexts.java b/sponge/src/main/java/com/griefdefender/util/SpongeContexts.java new file mode 100644 index 0000000..aed2293 --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/SpongeContexts.java @@ -0,0 +1,30 @@ +package com.griefdefender.util; + +import com.griefdefender.api.claim.ClaimType; +import org.spongepowered.api.service.context.Context; + +public class SpongeContexts { + + public static final Context GLOBAL_DEFAULT_CONTEXT = new Context("gd_claim_default", "global"); + public static final Context ADMIN_DEFAULT_CONTEXT = new Context("gd_claim_default", "admin"); + public static final Context BASIC_DEFAULT_CONTEXT = new Context("gd_claim_default", "basic"); + public static final Context SUBDIVISION_DEFAULT_CONTEXT = new Context("gd_claim_default", "subdivision"); + public static final Context TOWN_DEFAULT_CONTEXT = new Context("gd_claim_default", "town"); + public static final Context WILDERNESS_DEFAULT_CONTEXT = new Context("gd_claim_default", "wilderness"); + public static final Context WORLD_DEFAULT_CONTEXT = new Context("gd_claim_default", "world"); + + /** + * Override contexts are used to force a permission to a {@link ClaimType}. + */ + public static final Context GLOBAL_OVERRIDE_CONTEXT = new Context("gd_claim_override", "global"); + public static final Context ADMIN_OVERRIDE_CONTEXT = new Context("gd_claim_override", "admin"); + public static final Context BASIC_OVERRIDE_CONTEXT = new Context("gd_claim_override", "basic"); + /** + * Used to override a single claim only. + */ + //public static final Context CLAIM_OVERRIDE_CONTEXT = new Context("gd_claim_override", "CLAIM"); + public static final Context SUBDIVISION_OVERRIDE_CONTEXT = new Context("gd_claim_override", "subdivision"); + public static final Context TOWN_OVERRIDE_CONTEXT = new Context("gd_claim_override", "tonw"); + public static final Context WILDERNESS_OVERRIDE_CONTEXT = new Context("gd_claim_override", "wilderness"); + public static final Context WORLD_OVERRIDE_CONTEXT = new Context("gd_claim_override", "world"); +} diff --git a/sponge/src/main/java/com/griefdefender/util/SpongeUtil.java b/sponge/src/main/java/com/griefdefender/util/SpongeUtil.java new file mode 100644 index 0000000..5af2f5e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/SpongeUtil.java @@ -0,0 +1,106 @@ +package com.griefdefender.util; + +import com.griefdefender.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; +import net.kyori.text.Component; +import net.kyori.text.serializer.gson.GsonComponentSerializer; +import org.spongepowered.api.service.context.Context; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.text.Text; +import org.spongepowered.api.text.chat.ChatType; +import org.spongepowered.api.text.chat.ChatTypes; +import org.spongepowered.api.text.serializer.TextSerializers; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class SpongeUtil { + + public static org.spongepowered.api.service.context.Context getSpongeContext(com.griefdefender.api.permission.Context context) { + return new org.spongepowered.api.service.context.Context(context.getKey(), context.getValue()); + } + + public static org.spongepowered.api.service.context.Context getSpongeContext(ContextSet context) { + Context spongeContext = null; + for (Map.Entry<String, String> mapEntry : context) { + spongeContext = new Context(mapEntry.getKey(), mapEntry.getValue()); + break; + } + + return spongeContext; + } + + public static Text getSpongeText(Component component) { + if (component == null) { + return Text.EMPTY; + } + return TextSerializers.JSON.deserialize(GsonComponentSerializer.INSTANCE.serialize(component)); + } + + public static Set<Context> getSpongeContexts(com.griefdefender.api.permission.Context context) { + final Set<Context> spongeContexts = new HashSet<>(); + spongeContexts.add(new Context(context.getKey(), context.getValue())); + return spongeContexts; + } + + public static Set<Context> getSpongeContexts(Set<com.griefdefender.api.permission.Context> contexts) { + final Set<Context> spongeContexts = new HashSet<>(); + for (com.griefdefender.api.permission.Context gpContext : contexts) { + spongeContexts.add(new Context(gpContext.getKey(), gpContext.getValue())); + } + + return spongeContexts; + } + + public static Set<Context> getSpongeContexts(ContextSet contexts) { + final Set<Context> spongeContexts = new HashSet<>(); + for (Map.Entry<String, String> mapEntry : contexts) { + spongeContexts.add(new Context(mapEntry.getKey(), mapEntry.getValue())); + } + + return spongeContexts; + } + + public static Set<com.griefdefender.api.permission.Context> fromSpongeContexts(Set<Context> contexts) { + final Set<com.griefdefender.api.permission.Context> gpContexts = new HashSet<>(); + for (Context spongeContext : contexts) { + gpContexts.add(new com.griefdefender.api.permission.Context(spongeContext.getKey(), spongeContext.getValue())); + } + return gpContexts; + } + + public static Component fromSpongeText(Text text) { + return GsonComponentSerializer.INSTANCE.deserialize(TextSerializers.JSON.serialize(text)); + } + + public static Tristate fromSpongeTristate(org.spongepowered.api.util.Tristate value) { + if (value == org.spongepowered.api.util.Tristate.UNDEFINED) { + return Tristate.UNDEFINED; + } + + return Tristate.fromBoolean(value.asBoolean()); + } + + public static org.spongepowered.api.util.Tristate getSpongeTristate(Tristate value) { + if (value == Tristate.UNDEFINED) { + return org.spongepowered.api.util.Tristate.UNDEFINED; + } + + return org.spongepowered.api.util.Tristate.fromBoolean(value.asBoolean()); + } + + public static ChatType getSpongeChatType(com.griefdefender.api.ChatType chatType) { + switch (chatType.getName()) { + case "CHAT" : + return ChatTypes.CHAT; + case "ACTION_BAR" : + return ChatTypes.ACTION_BAR; + } + return ChatTypes.SYSTEM; + } + + public static Tristate getPermissionValue(Subject subject, ContextSet contexts, String permission) { + return fromSpongeTristate(subject.getPermissionValue(getSpongeContexts(contexts), permission)); + } +} diff --git a/sponge/src/main/java/com/griefdefender/util/TaskUtil.java b/sponge/src/main/java/com/griefdefender/util/TaskUtil.java new file mode 100644 index 0000000..918417e --- /dev/null +++ b/sponge/src/main/java/com/griefdefender/util/TaskUtil.java @@ -0,0 +1,58 @@ +/* + * This file is part of GriefDefender, licensed under the MIT License (MIT). + * + * Copyright (c) bloodmc + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.griefdefender.util; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public class TaskUtil { + + public static long computeDelay(int targetHour, int targetMin, int targetSec) + { + LocalDateTime localNow = LocalDateTime.now(); + ZoneId currentZone = ZoneId.systemDefault(); + ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone); + ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec); + if(zonedNow.compareTo(zonedNextTarget) > 0) { + zonedNextTarget = zonedNextTarget.plusDays(1); + } + + Duration duration = Duration.between(zonedNow, zonedNextTarget); + return duration.getSeconds(); + } + + public static ZonedDateTime getNextTargetZoneDate(int targetHour, int targetMin, int targetSec) { + LocalDateTime localNow = LocalDateTime.now(); + ZoneId currentZone = ZoneId.systemDefault(); + ZonedDateTime zonedNow = ZonedDateTime.of(localNow, currentZone); + ZonedDateTime zonedNextTarget = zonedNow.withHour(targetHour).withMinute(targetMin).withSecond(targetSec); + if(zonedNow.compareTo(zonedNextTarget) > 0) { + zonedNextTarget = zonedNextTarget.plusDays(1); + } + return zonedNextTarget; + } +} diff --git a/sponge/src/main/resources/1.12.2.json b/sponge/src/main/resources/1.12.2.json new file mode 100644 index 0000000..a37ddd1 --- /dev/null +++ b/sponge/src/main/resources/1.12.2.json @@ -0,0 +1,250 @@ +{ + "version": "1.12.2", + "libraries": [ + { + "name": "com.griefdefender:api:1.0.0", + "sha1": "6a52235cf4323e29572c1d45cdc56373404448b4", + "path": "com/griefdefender/api/1.0.0-SNAPSHOT/api-1.0.0-20190906.173641-10.jar", + "url": "https://repo.glaremasters.me/repository/bloodshot/com/griefdefender/api/1.0.0-SNAPSHOT/api-1.0.0-20190906.173641-10.jar" + }, + { + "name": "com.griefdefender:reflect-helper:1.0", + "sha1": "7a50bffa9f0062ac4ca376d95a0e6599aa5f3257", + "path": "com/griefdefender/reflect-helper/1.0/reflect-helper-1.0.jar", + "url": "https://repo.glaremasters.me/repository/bloodshot/com/griefdefender/reflect-helper/1.0/reflect-helper-1.0.jar" + }, + { + "name": "com.googlecode.json-simple:json-simple:1.1.1", + "sha1": "c9ad4a0850ab676c5c64461a05ca524cdfff59f1", + "path": "com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar", + "url": "https://repo1.maven.org/maven2/com/googlecode/json-simple/json-simple/1.1.1/json-simple-1.1.1.jar" + }, + { + "name": "org.ow2.asm:asm-debug-all:5.2", + "sha1": "3354e11e2b34215f06dab629ab88e06aca477c19", + "path": "org/ow2/asm/asm-debug-all/5.2/asm-debug-all-5.2.jar", + "relocate": "org.ow2.asm:asm", + "url": "https://repo1.maven.org/maven2/org/ow2/asm/asm-debug-all/5.2/asm-debug-all-5.2.jar" + }, + { + "name": "me.lucko:jar-relocator:1.3", + "sha1": "90c6b66b8535f2f5eefc108e34e50f3e02e1f4cf", + "path": "me/lucko/jar-relocator/1.3/jar-relocator-1.3.jar", + "relocate": "me.lucko:lucko", + "url": "https://repo1.maven.org/maven2/me/lucko/jar-relocator/1.3/jar-relocator-1.3.jar" + }, + { + "name": "co.aikar:acf-core:0.5.0-SNAPSHOT", + "sha1": "72aff420c92e4cb381ba8bccceb0b37cba7a9496", + "path": "co/aikar/acf-core/0.5.0-SNAPSHOT/acf-core-0.5.0-20190722.233344-152.jar", + "url": "https://repo.glaremasters.me/repository/public/co/aikar/acf-core/0.5.0-SNAPSHOT/acf-core-0.5.0-20190722.233344-152.jar" + }, + { + "name": "co.aikar:acf-sponge:0.5.0-SNAPSHOT", + "sha1": "c73413c215cfad8f0c2306ed156df74ebdf18d47", + "path": "co/aikar/acf-sponge/0.5.0-SNAPSHOT/acf-sponge-0.5.0-20190722.233405-145.jar", + "url": "https://repo.glaremasters.me/repository/public/co/aikar/acf-sponge/0.5.0-SNAPSHOT/acf-sponge-0.5.0-20190722.233405-145.jar" + }, + { + "name": "co.aikar:locales:1.0-SNAPSHOT", + "sha1": "09c89ff1a611600186edf8482d1059544875582b", + "path": "co/aikar/locales/1.0-SNAPSHOT/locales-1.0-20181221.115311-17.jar", + "url": "https://repo.glaremasters.me/repository/public/co/aikar/locales/1.0-SNAPSHOT/locales-1.0-20181221.115311-17.jar" + }, + { + "name": "co.aikar:minecraft-timings:1.0.4", + "sha1": "7ed9d44840cd2c0f77b7c5276d60ca901b146332", + "path": "co/aikar/minecraft-timings/1.0.4/minecraft-timings-1.0.4.jar", + "url": "https://repo.glaremasters.me/repository/public/co/aikar/minecraft-timings/1.0.4/minecraft-timings-1.0.4.jar" + }, + { + "name": "co.aikar:Table:1.0.0-SNAPSHOT", + "sha1": "ccbfaea11c65e6d7173226d318c577c439673b3a", + "path": "co/aikar/Table/1.0.0-SNAPSHOT/Table-1.0.0-20180331.054128-7.jar", + "url": "https://repo.glaremasters.me/repository/public/co/aikar/Table/1.0.0-SNAPSHOT/Table-1.0.0-20180331.054128-7.jar" + }, + { + "name": "net.jodah:expiringmap:0.5.9", + "sha1": "b93ac8a915e38beadc20c3cc284506e15478fd7b", + "path": "net/jodah/expiringmap/0.5.9/expiringmap-0.5.9.jar", + "relocate": "net.jodah:jodah", + "url": "https://repo1.maven.org/maven2/net/jodah/expiringmap/0.5.9/expiringmap-0.5.9.jar" + }, + { + "name": "aopalliance:aopalliance:1.0", + "sha1": "0235ba8b489512805ac13a8f9ea77a1ca5ebe3e8", + "path": "aopalliance/aopalliance/1.0/aopalliance-1.0.jar", + "relocate": "org.aopalliance:aopalliance", + "url": "https://repo1.maven.org/maven2/aopalliance/aopalliance/1.0/aopalliance-1.0.jar" + }, + { + "name": "com.flowpowered:flow-math:1.0.3", + "sha1": "d98020239e5015091ad3be927cef9dea0d61a234", + "path": "com/flowpowered/flow-math/1.0.3/flow-math-1.0.3.jar", + "url": "https://repo1.maven.org/maven2/com/flowpowered/flow-math/1.0.3/flow-math-1.0.3.jar" + }, + { + "name": "com.google.inject:guice:4.1.0", + "sha1": "eeb69005da379a10071aa4948c48d89250febb07", + "path": "com/google/inject/guice/4.1.0/guice-4.1.0.jar", + "url": "https://repo1.maven.org/maven2/com/google/inject/guice/4.1.0/guice-4.1.0.jar" + }, + { + "name": "javax.inject:javax.inject:1", + "sha1": "6975da39a7040257bd51d21a231b76c915872d38", + "path": "javax/inject/javax.inject/1/javax.inject-1.jar", + "url": "https://repo1.maven.org/maven2/javax/inject/javax.inject/1/javax.inject-1.jar" + }, + { + "name": "com.squareup.okhttp3:okhttp:3.14.2", + "sha1": "eaed79ed6bc1e14fad462172b6a09524545b165c", + "path": "com/squareup/okhttp3/okhttp/3.14.2/okhttp-3.14.2.jar", + "relocate": "okhttp3:okhttp3", + "url": "https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp/3.14.2/okhttp-3.14.2.jar" + }, + { + "name": "com.squareup.okio:okio:2.2.2", + "sha1": "36f483536153f15339a8b48d508e22be7c9c531a", + "path": "com/squareup/okio/okio/2.2.2/okio-2.2.2.jar", + "relocate": "okio:okio", + "url": "https://repo1.maven.org/maven2/com/squareup/okio/okio/2.2.2/okio-2.2.2.jar" + }, + { + "name": "me.lucko.luckperms:luckperms-api:4.4", + "sha1": "e0356ab83e426ff5e51b3c596ffac8750905af64", + "path": "me/lucko/luckperms/luckperms-api/4.4/luckperms-api-4.4.jar", + "relocate": "me.lucko:lucko", + "url": "https://repo1.maven.org/maven2/me/lucko/luckperms/luckperms-api/4.4/luckperms-api-4.4.jar" + }, + { + "name": "com.github.ben-manes.caffeine:caffeine:2.7.0", + "sha1": "c3af06be4a7d4e769fce2cef5e77d3becad9818a", + "path": "com/github/ben-manes/caffeine/caffeine/2.7.0/caffeine-2.7.0.jar", + "relocate": "com.github.benmanes.caffeine:caffeine", + "url": "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/caffeine/2.7.0/caffeine-2.7.0.jar" + }, + { + "name": "commons-io:commons-io:2.6", + "sha1": "815893df5f31da2ece4040fe0a12fd44b577afaf", + "path": "org/apache/commons/commons-io/2.6/commons-io-2.6.jar", + "relocate": "org.apache.commons.io:commonsio", + "url": "https://repo1.maven.org/maven2/commons-io/commons-io/2.6/commons-io-2.6.jar" + }, + { + "name": "org.apache.commons:commons-lang3:3.9", + "sha1": "0122c7cee69b53ed4a7681c03d4ee4c0e2765da5", + "path": "org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar", + "relocate": "org.apache.commons.lang3:commonslang3", + "url": "https://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.9/commons-lang3-3.9.jar" + }, + { + "name": "org.spongepowered:configurate-core:3.7-SNAPSHOT", + "sha1": "e596c439ac71fa2ea5c48f8ba97a7dc6f4c77b16", + "path": "org/spongepowered/configurate-core/3.7-SNAPSHOT/configurate-core-3.7-20190531.182437-11.jar", + "relocate": "ninja.leaping.configurate:configurate", + "url": "https://repo.spongepowered.org/maven/org/spongepowered/configurate-core/3.7-SNAPSHOT/configurate-core-3.7-20190531.182437-11.jar" + }, + { + "name": "org.spongepowered:configurate-gson:3.7-SNAPSHOT", + "sha1": "265a94f16583621f497eeecc356f35f983484dde", + "path": "org/spongepowered/configurate-gson/3.7-SNAPSHOT/configurate-gson-3.7-20190531.182438-11.jar", + "relocate": "ninja.leaping.configurate:configurate", + "url": "https://repo.spongepowered.org/maven/org/spongepowered/configurate-gson/3.7-SNAPSHOT/configurate-gson-3.7-20190531.182438-11.jar" + }, + { + "name": "org.spongepowered:configurate-hocon:3.7-SNAPSHOT", + "sha1": "af48dcb9e7456f2f81a633f62ae5c55e5215c4af", + "path": "org/spongepowered/configurate-hocon/3.7-SNAPSHOT/configurate-hocon-3.7-20190531.182439-11.jar", + "relocate": "ninja.leaping.configurate:configurate", + "url": "https://repo.spongepowered.org/maven/org/spongepowered/configurate-hocon/3.7-SNAPSHOT/configurate-hocon-3.7-20190531.182439-11.jar" + }, + { + "name": "org.spongepowered:configurate-yaml:3.7-SNAPSHOT", + "sha1": "c66110f5ae0098c450e048f78b322590d2e24d06", + "path": "org/spongepowered/configurate-yaml/3.7-SNAPSHOT/configurate-yaml-3.7-20190531.182442-11.jar", + "relocate": "ninja.leaping.configurate:configurate", + "url": "https://repo.spongepowered.org/maven/org/spongepowered/configurate-yaml/3.7-SNAPSHOT/configurate-yaml-3.7-20190531.182442-11.jar" + }, + { + "name": "com.typesafe:config:1.3.1", + "sha1": "2cf7a6cc79732e3bdf1647d7404279900ca63eb0", + "path": "com/typesafe/config/1.3.1/config-1.3.1.jar", + "relocate": "com.typesafe:typesafe", + "url": "https://repo1.maven.org/maven2/com/typesafe/config/1.3.1/config-1.3.1.jar" + }, + { + "name": "it.unimi.dsi:fastutil:8.2.3", + "sha1": "f3a26db2204f1779c9958a914422f84284e53a84", + "path": "it/unimi/dsi/fastutil/8.2.3/fastutil-8.2.3.jar", + "relocate": "it.unimi.dsi:fastutil", + "url": "https://repo1.maven.org/maven2/it/unimi/dsi/fastutil/8.2.3/fastutil-8.2.3.jar" + }, + { + "name": "org.jetbrains.kotlin:kotlin-stdlib:1.3.31", + "sha1": "11289d20fd95ae219333f3456072be9f081c30cc", + "path": "org/jetbrains/kotlin/kotlin-stdlib/1.3.31/kotlin-stdlib-1.3.31.jar", + "relocate": "org.jetbrains:jetbrains", + "url": "https://repo1.maven.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.31/kotlin-stdlib-1.3.31.jar" + }, + { + "name": "net.kyori:event-api:3.0.0", + "sha1": "4e207f07d2adaa15e174a085f65bc6ae5a81029e", + "path": "net/kyori/event-api/3.0.0/event-api-3.0.0.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/event-api/3.0.0/event-api-3.0.0.jar" + }, + { + "name": "net.kyori:event-method:3.0.0", + "sha1": "85fe9bbf8ebadde4c82602af29352ba5db06e8e5", + "path": "net/kyori/event-method/3.0.0/event-method-3.0.0.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/event-method/3.0.0/event-method-3.0.0.jar" + }, + { + "name": "net.kyori:event-method-asm:3.0.0", + "sha1": "69113430c1ba05c9d9fa6e48028edd53e3e16723", + "path": "net/kyori/event-method-asm/3.0.0/event-method-asm-3.0.0.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/event-method-asm/3.0.0/event-method-asm-3.0.0.jar" + }, + { + "name": "net.kyori:text-adapter-bukkit:3.0.3", + "sha1": "37033ab1173d73a62a087cbd5c8d356774f4cee3", + "path": "net/kyori/text-adapter-bukkit/3.0.3/text-adapter-bukkit-3.0.3.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-adapter-bukkit/3.0.3/text-adapter-bukkit-3.0.3.jar" + }, + { + "name": "net.kyori:text-adapter-bungeecord:3.0.2", + "sha1": "d57c245bdc182bdf37d1b7a32691859add018a2b", + "path": "net/kyori/text-adapter-bungeecord/3.0.2/text-adapter-bungeecord-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-adapter-bungeecord/3.0.2/text-adapter-bungeecord-3.0.2.jar" + }, + { + "name": "net.kyori:text-adapter-spongeapi:3.0.2", + "sha1": "8562afb1594a9d34b891f23add503741d0656873", + "path": "net/kyori/text-adapter-spongeapi/3.0.2/text-adapter-spongeapi-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-adapter-spongeapi/3.0.2/text-adapter-spongeapi-3.0.2.jar" + }, + { + "name": "net.kyori:text-api:3.0.2", + "sha1": "608cdb44a74bbd68745941760df730ed55e4b47c", + "path": "net/kyori/text-api/3.0.2/text-api-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-api/3.0.2/text-api-3.0.2.jar" + }, + { + "name": "net.kyori:text-serializer-gson:3.0.2", + "sha1": "9ac22f04f3504c52ff1618c5a8d9a6145d8d9c9e", + "path": "net/kyori/text-serializer-gson/3.0.2/text-serializer-gson-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-serializer-gson/3.0.2/text-serializer-gson-3.0.2.jar" + }, + { + "name": "net.kyori:text-serializer-legacy:3.0.2", + "sha1": "8acbfb36356259273a8e3a15782e4f2980375bc5", + "path": "net/kyori/text-serializer-legacy/3.0.2/text-serializer-legacy-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-serializer-legacy/3.0.2/text-serializer-legacy-3.0.2.jar" + }, + { + "name": "net.kyori:text-serializer-plain:3.0.2", + "sha1": "8d60703f579019f7c26959d2e46501c3d389b48d", + "path": "net/kyori/text-serializer-plain/3.0.2/text-serializer-plain-3.0.2.jar", + "url": "https://repo1.maven.org/maven2/net/kyori/text-serializer-plain/3.0.2/text-serializer-plain-3.0.2.jar" + } + ] +} \ No newline at end of file diff --git a/sponge/src/main/resources/LICENSE b/sponge/src/main/resources/LICENSE new file mode 100644 index 0000000..829a203 --- /dev/null +++ b/sponge/src/main/resources/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) bloodmc +Copyright (c) contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/sponge/src/main/resources/META-INF/griefdefender_at.cfg b/sponge/src/main/resources/META-INF/griefdefender_at.cfg new file mode 100644 index 0000000..0a55de2 --- /dev/null +++ b/sponge/src/main/resources/META-INF/griefdefender_at.cfg @@ -0,0 +1,5 @@ +public net.minecraft.world.gen.ChunkProviderServer field_73247_e # chunkLoader +public net.minecraft.world.gen.ChunkProviderServer field_73244_f # id2ChunkMap +public net.minecraft.world.gen.ChunkProviderServer field_186029_c # chunkGenerator +public net.minecraft.server.management.PlayerChunkMapEntry field_187286_f # chunk +public net.minecraft.server.management.PlayerChunkMapEntry field_187283_c # players \ No newline at end of file diff --git a/sponge/src/main/resources/assets/lang/en_US.conf b/sponge/src/main/resources/assets/lang/en_US.conf new file mode 100644 index 0000000..e2db9c2 --- /dev/null +++ b/sponge/src/main/resources/assets/lang/en_US.conf @@ -0,0 +1,637 @@ +GriefDefender { + descriptions { + abandon-all="Abandons ALL your claims." + abandon-claim="Abandons a claim." + abandon-top="Abandons top level claim." + buy-blocks="Purchases additional claim blocks with server money.\nNote: Requires economy plugin." + callback="Execute a callback registered as part of a Text object. Primarily for internal use." + claim-bank="Used to withdraw or deposit money for use in claim." + claim-clear="Allows clearing of entities within one or more claims." + claim-debug="Toggles claim flag debug mode." + claim-farewell="Sets the farewell message of your claim." + claim-greeting="Sets the greeting message of your claim." + claim-ignore="Toggles ignore claims mode." + claim-info="Displays all known information for claim you are standing in." + claim-inherit="Toggles whether this claim should inherit permissions from parent(s)." + claim-list="Lists all known claims in area." + claim-name="Sets the claim name." + claim-restore="Restores claim to its natural state. Use with caution." + claim-setspawn="Sets the claim spawn for players." + claim-spawn="Teleports you to claim spawn if available." + claim-transfer="Transfers a basic or admin claim to another player." + claim-worldedit="Uses the worldedit selection to create a claim." + cuboid="Toggles cuboid claims mode." + debug="Captures all GD actions for debugging purposes." + delete-all="Delete all of another player's claims." + delete-all-admin="Deletes all administrative claims." + delete-claim="Deletes the claim you're standing in, even if it's not your claim." + delete-top="Deletes the claim you're standing in, even if it's not your claim." + flag-claim="Gets/Sets claim flags in the claim you are standing in." + flag-group="Gets/Sets flag permission for a group in claim you are standing in." + flag-player="Gets/Sets flag permission for a player in claim you are standing in." + flag-reset="Resets a claim to flag defaults." + mode-admin="Switches the claim tool to administrative claims mode" + mode-basic="Switches the claim tool back to basic claims mode." + mode-nature="Switches the claim tool to restoration mode." + mode-subdivision="Switches the claim tool to subdivision mode, used to subdivide your claims." + mode-town="Switches the claim tool to town claims mode." + option-claim="Gets/Sets claim options in the claim you are standing in." + permission-group="Sets a permission on a group with a claim context." + permission-player="Sets a permission on a player with a claim context." + player-adjust-bonus-blocks="Updates a player's bonus claim block total." + player-info="Shows information about a player." + player-set-accrued-blocks="Updates a player's accrued claim block total." + reload="Reloads GriefDefender's configuration settings." + schematic="Manages claim schematics. Use '/claimschematic create <name>' to create a live backup of claim." + sell-blocks="Sell your claim blocks for server money.\nNote: Requires economy plugin." + sell-claim="Puts your claim up for sale. Use /claimsell amount.\nNote: Requires economy plugin." + town-chat="Toggles town chat." + town-tag="Sets the tag of your town." + trust-group="Grants a group access to your claim.\nAccessor: access to interact with all blocks except inventory.\nContainer: access to interact with all blocks including inventory.\nBuilder: access to everything above including ability to place and break blocks.\nManager: access to everything above including ability to manage claim settings." + trust-group-all="Grants a group access to ALL your claim(s).\nAccessor: access to interact with all blocks except inventory.\nContainer: access to interact with all blocks including inventory.\nBuilder: access to everything above including ability to place and break blocks.\nManager: access to everything above including ability to manage claim settings." + trust-player="Grants a player access to your claim.\nAccessor: access to interact with all blocks except inventory.\nContainer: access to interact with all blocks including inventory.\nBuilder: access to everything above including ability to place and break blocks.\nManager: access to everything above including ability to manage claim settings." + trust-player-all="Grants a player access to ALL your claim(s).\nAccessor: access to interact with all blocks except inventory.\nContainer: access to interact with all blocks including inventory.\nBuilder: access to everything above including ability to place and break blocks.\nManager: access to everything above including ability to manage claim settings." + untrust-group="Revokes group access to your claim." + untrust-group-all="Revokes group access to all your claims." + untrust-player="Revokes player access to your claim." + untrust-player-all="Revokes player access to all your claims." + version="Displays GriefDefender's version information." + } + messages { + abandon-all-delay-warning="&aThese claims were recently created and cannot be abandoned." + abandon-all-warning="&6Are you sure you want to abandon &cALL&6 your claims?" + abandon-claim-delay-warning="&aThis claim was recently created and cannot be abandoned until &6{date}&a." + abandon-claim-failed="&aCould not abandon claim. Claim result was &f{result}&a." + abandon-claim-missing="&cNo claim found. Stand in the claim you want to abandon, or consider &f/abandonall&c." + abandon-other-success="&6{player}&a's claim has been abandoned. &6{player}&a now has &6{amount}&a available claim blocks." + abandon-success="&aClaim abandoned. You now have &6{amount}&a available claim blocks." + abandon-top-level="&cThis claim cannot be abandoned as it contains one or more child claims. In order to abandon a claim with child claims, you must use &f/abandontop&c instead." + abandon-town-children="&cYou do not have permission to abandon a town with child claims you do not own. Use &f/ignoreclaims&c or have the child claim owner abandon their claim first. If you just want to abandon the town without affecting children then use &f/abandon&c instead." + abandon-warning="&6Are you sure you want to abandon this claim? It will no longer be protected from grief." + adjust-accrued-blocks-success="&aAdjusted &6{player}&a's accrued claim blocks by &6{amount}&a. New total accrued blocks: &6{total}&a." + adjust-bonus-blocks-success="&aAdjusted &6{player}&a's bonus claim blocks by &6{amount}&a. New total bonus blocks: &6{total}&a." + bank-click-view-transactions="Click here to view bank transactions" + bank-deposit="&aSuccessful deposit of &6{amount}&a into bank." + bank-deposit-no-funds="&cYou do not have enough funds to deposit into the bank." + bank-info="&aBalance: &6{balance}&a \nTax: &6{tax-amount}&f due in &7{time-remaining}&a \nTax Owed: &6{tax-balance}." + bank-no-permission="&cYou don't have permission to manage &6{player}&c's claim bank." + bank-tax-system-disabled="&cThe bank/tax system is not enabled. If you want it enabled, set 'bank-tax-system' to true in config." + bank-title-transactions="Bank Transactions" + bank-withdraw="&aSuccessful withdraw of &6{amount} &afrom bank." + bank-withdraw-no-funds="&cThe claim bank has a remaining balance of &a{balance}&c and does not have enough funds to withdraw &a{amount}&c." + block-claimed="&aThat block has been claimed by &6{player}&a." + block-not-claimed="&cNo one has claimed this block." + block-sale-value="&aEach claim block is worth &6{amount}&a. You have &6{total}&a available for sale." + claim-above-level="&cUnable to claim block as it is above your maximum claim level limit of &a{limit}&c." + claim-action-not-available="&cThis action is not available in {type}&c." + claim-automatic-notification="&cThis chest and nearby blocks are protected." + claim-below-level="&cUnable to claim block as it is below your minimum claim level limit of &a{limit}&c." + claim-chest-confirmation="&cThis chest is protected." + claim-chest-outside-level="&cThis chest can't be protected as the position is outside your claim level limits of &a{min-level}&c and &a{max-level}&c." + claim-children-warning="&6This claim includes child claims. If you're sure you want to delete it, use &f/deleteclaim&6 again." + claim-context-not-found="&cContext &f{context}&c was not found." + claim-disabled-world="&cClaims are disabled in this world." + claim-expired-inactivity="&6{player}'s &cclaim with id &f{uuid}&c has expired and has been removed due to inactivity." + claim-farewell="&aSet claim farewell to {farewell}&a." + claim-farewell-clear="&aThe claim farewell message has been cleared." + claim-farewell-invalid="&cClaim flag &f{flag}&c is invalid." + claim-greeting="&aSet claim greeting to {greeting}&a." + claim-greeting-clear="&aThe claim greeting message has been cleared." + claim-ignore="&aNow ignoring claims." + claim-last-active="&aClaim last active &6{date}&a." + claim-mode-start="{type}&a corner set! Right click again at the opposite corner to claim a rectangle of land. To cancel, type &f/claim&a to exit claim mode." + claim-name="&aSet claim name to {name}&a." + claim-no-claims="&cYou don't have any land claims." + claim-no-set-home="&cYou must be trusted in order to use /sethome here." + claim-not-found="&cThere's no claim here." + claim-not-yours="&cThis isn't your claim." + claim-owner-already="&cYou are already the claim owner." + claim-owner-only="&cOnly &6{player}&c can modify this claim." + claim-protected-entity="&cThat belongs to &6{player}&c." + claim-respecting="&aNow respecting claims." + claim-restore-success="&aSuccessfully restored claim." + claim-show-nearby="&aFound &6{amount}&a nearby claims." + claim-size-max="&cThe claim &6{axis}&c size of &a{size}&c exceeds the max size of &a{max-size}&c.\nThe area needs to be a minimum of &a{min-area}&c and a max of &a{max-area}" + claim-size-min="&cThe claim &6{axis}&c size of &a{size}&c is below the min size of &a{min-size}&c.\nThe area needs to be a minimum of &a{min-area}&c and a max of &a{max-area}" + claim-size-need-blocks-2d="&cYou don't have enough blocks for this claim size.\nYou need &a{block-amount}&c more blocks." + claim-size-need-blocks-3d="&cYou don't have enough blocks for this claim size.\nYou need &a{chunk-amount}&c more chunks. &f({block-amount})" + claim-size-too-small="&cThe selected claim size of &a{width}&fx&a{length}&c would be too small. A claim must be at least &a{min-width}&fx&a{min-length}&c in size." + claim-start="{type}&a corner set! Use the {item}&a again at the opposite corner to claim a rectangle of land. To cancel, put your {item}&a away." + claim-too-far="&cThat's too far away." + claim-transfer-exceeds-limit="&cClaim could not be transferred as it would exceed the new owner's creation limit." + claim-transfer-success="&aClaim transferred." + claim-type-not-found="&cNo {type}&c claims found." + claiminfo-ui-admin-settings="Admin Settings" + claiminfo-ui-bank-info="Bank Info" + claiminfo-ui-claim-expiration="Claim Expiration" + claiminfo-ui-click-admin="Click here to view admin settings" + claiminfo-ui-click-bank="Click here to check bank information" + claiminfo-ui-click-change-claim="Click here to change claim to {type}" + claiminfo-ui-click-toggle="Click here to toggle value" + claiminfo-ui-deny-messages="Deny Messages" + claiminfo-ui-flag-overrides="Flag Overrides" + claiminfo-ui-for-sale="For Sale" + claiminfo-ui-inherit-parent="Inherit Parent" + claiminfo-ui-last-active="Last Active" + claiminfo-ui-north-corners="North Corners" + claiminfo-ui-pvp-override="PvP Override" + claiminfo-ui-requires-claim-blocks="Requires Claim Blocks" + claiminfo-ui-return-bankinfo="Return to bank info" + claiminfo-ui-return-claiminfo="Return to claim info" + claiminfo-ui-return-settings="Return to standard Settings" + claiminfo-ui-size-restrictions="Size Restrictions" + claiminfo-ui-south-corners="South Corners" + claiminfo-ui-teleport-direction="Click here to teleport to {direction}&f corner of claim" + claiminfo-ui-teleport-feature="You do not have permission to use the teleport feature in this claim" + claiminfo-ui-teleport-spawn="Click here to teleport to claim spawn" + claiminfo-ui-title-claiminfo="Claim Info" + claiminfo-ui-town-settings="Town Settings" + claimlist-ui-click-info="Click to check more info" + claimlist-ui-click-purchase="Click here to purchase claim" + claimlist-ui-click-teleport-target="Click here to teleport to {name}&f {target}&f in &6{world}" + claimlist-ui-click-toggle-value="Click here to toggle {type} value" + claimlist-ui-click-view-children="Click here to view child claim list" + claimlist-ui-click-view-claims="Click here to view the claims you own" + claimlist-ui-no-claims-found="No claims found in world." + claimlist-ui-return-claimlist="Return to claims list" + claimlist-ui-title="Claim list" + claimlist-ui-title-child-claims="Child Claims" + command-blocked="&cThe command &f{command}&c has been blocked by claim owner &6{player}&c." + command-claimban-success-block="&aSuccessfully &cBANNED&a block with id {id}&a." + command-claimban-success-entity="&aSuccessfully &cBANNED&a entity with id {id}&a." + command-claimban-success-item="&aSuccessfully &cBANNED&a item with id {id}&a." + command-claimbuy-title="&bClaims for sale" + command-claimclear-killed="&cKilled &6{amount}&a entities of type {type}&f." + command-claimclear-no-entities="&cCould not locate any entities of type {type}&c." + command-claimclear-uuid-deny="&cOnly administrators may clear claims by UUID." + command-claimflagdebug-disabled="Claim flags debug &cOFF" + command-claimflagdebug-enabled="Claim flags debug &aON" + command-claiminfo-not-found="&cNo valid player or claim UUID found." + command-claiminfo-uuid-required="&cClaim UUID is required if executing from non-player source." + command-claiminherit-disabled="Parent claim inheritance &cOFF" + command-claiminherit-enabled="Parent claim inheritance &aON" + command-claimmode-disabled="Claim mode &cOFF" + command-claimmode-enabled="Claim mode &aON&f\n&aLeft-click to inspect.\n&aRight-click to claim.&b\nNote&f: &aUse &f/claim&a to exit mode." + command-claimspawn-not-found="&aCould not locate a claim with name {name}&a." + command-claimunban-success-block="&aSuccessfully &6UNBANNED&a block with id {id}&a." + command-claimunban-success-entity="&aSuccessfully &6UNBANNED&a entity with id {id}&a." + command-claimunban-success-item="&aSuccessfully &6UNBANNED&a item with id {id}&a." + command-cuboid-disabled="&aNow claiming in &d2D&a mode." + command-cuboid-enabled="&aNow claiming in &d3D&a mode." + command-execute-failed="&cFailed to execute command '{command} {args}'" + command-giveblocks-confirmation="&6Are you sure you want to give {player}&6 {amount}&6 claim blocks?" + command-giveblocks-confirmed="&aClaim block transfer complete." + command-giveblocks-not-enough="&cNot enough claim blocks! You only have {amount}&c available claim blocks to transfer.\n&bNote&f: This amount does not include initial claim blocks. It only includes accrued and bonus." + command-giveblocks-received="&aYou have been given {amount}&a claim blocks from {player}&a." + command-inherit-only-child="&cThis command can only be used in child claims." + command-invalid="&cNo valid command entered." + command-invalid-amount="&cInvalid amount &6{amount}&c entered." + command-invalid-claim="&cThis command cannot be used in {type}&c claims." + command-invalid-group="&cGroup &6{group}&c is not valid." + command-invalid-player="&cPlayer &6{player}&c is not valid." + command-invalid-player-group="&cNot a valid player or group." + command-invalid-type="&cInvalid type {type}&c specified." + command-not-available-economy="&cThis command is not available while server is in economy mode." + command-option-exceeds-admin="&cOption value of &a'{value}&c' exceeds admin set value of '&a{admin-value}&c'. Adjusting to admin value..." + command-pet-confirmation="&aPet transferred." + command-pet-invalid="&cPet type {type} is not currently supported for transfer." + command-pet-transfer-cancel="&aPet giveaway cancelled." + command-pet-transfer-ready="&aReady to transfer! Right-click the pet you'd like to give away, or cancel by left-clicking." + command-player-not-found="&cPlayer '&6{player}&c' could not be found." + command-world-not-found="&cWorld '&6{world}&c' could not be found." + command-worldedit-missing="&cThis command requires WorldEdit to be installed on server." + create-cancel="&cThe creation of this claim has been cancelled." + create-cuboid-disabled="&cThe creation of &d3D&c cuboid claims has been disabled by an administrator.\nYou can only create &d3D&c claims as an Admin or on a &d2D&c claim that you own." + create-failed-claim-limit="&cYou've reached your limit of &a{limit}&c on {type}&c claims. Use &f/abandon&c to remove one before creating another." + create-failed-result="&aThe claim failed to be created due to : &6{reason}&a." + create-insufficient-blocks-2d="&cYou don't have enough blocks to claim this area.\nYou need &a{block-amount}&c more blocks." + create-insufficient-blocks-3d="&cYou don't have enough blocks to claim this area.\nYou need &a{chunk-amount}&c more chunks. &f({block-amount})" + create-overlap="&cYou can't create a claim here because it would overlap an existing claim." + create-overlap-player="&cYou can't create a claim here because it would overlap &6{player}&c's claim." + create-overlap-short="&cYour selected area overlaps an existing claim." + create-subdivision-fail="&cNo claim exists at selected corner. Please click a valid block location within parent claim in order to create your subdivision." + create-subdivision-only="&cUnable to create claim. Only subdivisions can be created at a single block location." + create-success="{type}&a created! Use &f/trust&a to share it with friends." + debug-error-upload="&cError uploading content {content}&c." + debug-no-records="&cNo debug records to paste!" + debug-paste-success="&aPaste success!" + debug-record-end="Record end" + debug-record-start="Record start" + debug-time-elapsed="Time elapsed" + delete-all-player-failed="&aCould not delete all of &6{player}&a's claims. Claim result was &f{result}&a." + delete-all-player-success="&aDeleted all of &6{player}&a's claims." + delete-all-player-warning="&6Are you sure you want to delete all of {player}&6's claims?" + delete-all-type-deny="&cCould not delete all {type}&c claims. A plugin has denied it." + delete-all-type-success="&cDeleted all {type}&c claims." + delete-all-type-warning="&6Are you sure you want to delete all {type}&6 claims?" + delete-claim-failed="&aCould not delete claim. Claim result was &f{result}&a." + delete-claim-success="&aDeleted {player}&a's claim." + delete-claim-warning="&6Are you sure you want to delete {player}&6's claim?" + economy-balance="&aYour new balance is &6{balance}&a." + economy-block-available-purchase-2d="&aYou have enough funds to claim up to &6{block-amount} &amore blocks." + economy-block-available-purchase-3d="&aYou have enough funds to claim up to &6{chunk-amount} &amore chunks. &f({block-amount})" + economy-block-buy-invalid="&cBlock count must be greater than 0." + economy-block-buy-sell-disabled="&cSorry, buying and selling claim blocks is disabled." + economy-block-cost="&aEach claim block costs &6{amount}&a." + economy-block-not-available="&cYou don't have that many claim blocks available for sale." + economy-block-only-buy="&cClaim blocks may only be purchased, not sold." + economy-block-only-sell="&cClaim blocks may only be sold, not purchased." + economy-block-purchase-confirmation="&aWithdrew &6{amount}&a from your account. You now have &6{balance}&a available claim blocks." + economy-block-purchase-cost="&aEach claim block costs &6{amount}&a. Your balance is &6{balance}&a." + economy-block-purchase-limit="&cThe new claim block total of &a{total}&c will exceed your claim block limit of &a{limit}&c. The transaction has been cancelled." + economy-block-sale-confirmation="&aDeposited &6{deposit}&a in your account. You now have &6{amount}&a available claim blocks." + economy-block-sell-error="&cCould not sell blocks. Reason: &f{reason}&c." + economy-claim-abandon-success="&aClaim(s) abandoned. You have been refunded a total of '&6{amount}&a'." + economy-claim-buy-cancelled="&cBuy cancelled! Could not buy claim from &6{player}&c. Result was &a{result}" + economy-claim-buy-confirmation="&6Are you sure you want to buy this claim for &a{amount}&6? Click confirm to proceed." + economy-claim-buy-confirmed="&aYou have successfully bought the claim for &6{amount}&a." + economy-claim-buy-not-enough-funds="&cYou do not have enough funds to purchase this claim for &a{amount}&c. You currently have a balance of &a{balance}&c and need &a{amount_required}&c more for purchase." + economy-claim-buy-transfer-cancelled="&cClaim transfer cancelled! Could not transfer owner &6{owner}&c to &6{player}&c. Result was &a{result}" + economy-claim-not-for-sale="&cThis claim is not for sale." + economy-claim-sale-cancelled="&aYou have cancelled your claim sale." + economy-claim-sale-confirmation="&6Are you sure you want to sell your claim for &a{amount}&6 ? If your claim is sold, all items and blocks will be transferred to the buyer. Click confirm if this is OK." + economy-claim-sale-confirmed="&aYou have successfully put your claim up for sale for the amount of &6{amount}&a." + economy-claim-sale-invalid-price="&cThe sale price of &a{amount}&c must be greater than or equal to &a0&c." + economy-claim-sold="&aYour claim sold! The amount of &6{amount}&a has been deposited into your account. Your total available balance is now &6{balance}&a." + economy-mode-block-sale-confirmation="&aDeposited &6{deposit}&a in your account. Your total balance is &6{balance}&a. You now have enough funds to claim up to &6{amount}&a more claim blocks." + economy-mode-resize-success-2d="&aClaim resized. Your new balance is &6{balance}&a. You have enough funds to claim up to &6{block-amount} &amore blocks." + economy-mode-resize-success-3d="&aClaim resized. Your new balance is &6{balance}&a. You have enough funds to claim up to &6{chunk-amount} &amore chunks. &f({block-amount})" + economy-not-enough-funds="&cYou do not have enough funds to purchase this land. Your current economy balance is '&a{balance}&c' but you require '&a{amount}&c' to complete the purchase." + economy-not-installed="&cEconomy plugin not installed!." + economy-player-not-found="&cNo economy account found for player &6{player}&c." + economy-remaining-funds="&aYou have &6{amount}&a available for claiming land." + economy-virtual-not-supported="&cEconomy plugin does not support virtual accounts which is required. Use another economy plugin or contact plugin dev for virtual account support." + economy-withdraw-error="&cCould not withdraw funds. Reason: &f{reason}&c." + feature-not-available="&cThis feature is currently being worked on and will be available in a future release." + flag-description-block-break="Controls whether a block can be broken.\n&dExample&f : To prevent any source from breaking dirt blocks, enter\n&a/cf block-break minecraft:dirt false\n&bNote&f : minecraft represents the modid and dirt represents the block id.\nSpecifying no modid will always default to minecraft." + flag-description-block-grow="Controls whether a block can grow.\n&dExample&f : To prevent a cactus from growing, enter\n&a/cf block-grow minecraft:cactus false\n&bNote&f : minecraft represents the modid and cactus represents the block id.\nSpecifying no modid will always default to minecraft." + flag-description-block-modify="Controls whether a block can be modified.\n&dExample&f : To prevent any source from igniting a block, enter\n&a/cf block-modify minecraft:fire false\n&bNote&f : minecraft represents the modid and fire represents the block id.\nSpecifying no modid will always default to minecraft." + flag-description-block-place="Controls whether a block can be placed.\n&dExample&f : To prevent any source from placing dirt blocks, enter\n&a/cf block-place minecraft:dirt false\n&bNote&f : minecraft represents the modid and dirt represents the block id.\nSpecifying no modid will always default to minecraft." + flag-description-block-spread="Controls whether a block can spread to another block.\n&dExample&f : To prevent fire from spreading, enter\n&a/cf block-spread any false context[source=fire]\n&bNote&f : 'any' represents any target block and fire represents the source block id.\nSpecifying no modid will always default to minecraft." + flag-description-collide-block="Controls whether an entity can collide with a block.\n&dExample&f : To prevent entity collisions with stone pressure plates, enter\n&a/cf collide-block minecraft:stone_pressure_plate false\n&bNote&f : minecraft represents the modid and stone_pressure_plate represents the block id.\nSpecifying no modid will always default to minecraft." + flag-description-collide-entity="Controls whether an entity can collide with an entity.\n&dExample&f : To prevent entity collisions with item frames, enter\n&a/cf collide-entity minecraft:item_frame false\n&bNote&f : minecraft represents the modid and item_frame represents the entity id.\nSpecifying no modid will always default to minecraft." + flag-description-command-execute="Controls whether a command can be executed.\n&dExample&f : To prevent pixelmon's command '/shop select' from being run, enter\n&a/cf command-execute pixelmon:shop[select] false\n&bNote&f : &o&6pixelmon&f represents the modid and &o&6shop&f represents the base command, and &o&6select&f represents the argument.\nSpecifying no modid will always default to minecraft." + flag-description-command-execute-pvp="Controls whether a command can be executed while engaged in PvP.\n&dExample&f : To prevent pixelmon's command '/shop select' from being run, enter\n&a/cf command-execute pixelmon:shop[select] false\n&bNote&f : &o&6pixelmon&f represents the modid and &o&6shop&f represents the base command, and &o&6select&f represents the argument.\nSpecifying no modid will always default to minecraft." + flag-description-custom-block-break="Controls whether blocks can be broken." + flag-description-custom-block-grow="Controls whether blocks can grow." + flag-description-custom-block-place="Controls whether blocks can be placed." + flag-description-custom-block-spread="Controls whether blocks can spread." + flag-description-custom-build="Controls whether actions are allowed against blocks and entities such as mining, placement, and interactions." + flag-description-custom-chest-access="Controls whether a player can access chest inventories." + flag-description-custom-chorus-fruit-teleport="Controls whether a player can use chorus fruit to teleport." + flag-description-custom-crop-growth="Controls whether crops can grow." + flag-description-custom-damage-animals="Controls whether animals can be damaged." + flag-description-custom-enderman-grief="Controls whether enderman can grief." + flag-description-custom-enderpearl="Controls whether enderpearl can be used." + flag-description-custom-enter-player="Controls whether a player can enter this claim." + flag-description-custom-exit-player="Controls whether a player can exit this claim." + flag-description-custom-exp-drop="Controls whether experience orbs can drop." + flag-description-custom-explosion-block="Controls whether explosions affect blocks." + flag-description-custom-explosion-creeper="Controls whether a creeper can explode." + flag-description-custom-explosion-entity="Controls whether explosions affect entities." + flag-description-custom-explosion-tnt="Controls whether tnt can explode." + flag-description-custom-fall-damage="Controls whether players can take fall damage." + flag-description-custom-fire-damage="Controls whether fire can cause damage." + flag-description-custom-fire-spread="Controls whether fire can spread." + flag-description-custom-grass-growth="Controls whether grass can grow." + flag-description-custom-ice-form="Controls whether ice can form." + flag-description-custom-ice-melt="Controls whether ice can melt." + flag-description-custom-interact-block="Controls whether players can interact with blocks.\n&bNote&f: This does not include inventory blocks such as chests." + flag-description-custom-interact-entity="Controls whether players can interact with entities.\n&bNote&f: This does not include chest access with entities such as horses." + flag-description-custom-interact-inventory="Controls whether players can interact with inventory." + flag-description-custom-invincible="Controls whether players are invincible against damage." + flag-description-custom-item-drop="Controls whether players can drop items." + flag-description-custom-item-pickup="Controls whether players can pickup items." + flag-description-custom-lava-flow="Controls whether lava can flow." + flag-description-custom-leaf-decay="Controls whether leaves can decay." + flag-description-custom-lighter="Controls whether a player can use flint and steel." + flag-description-custom-lightning="Controls whether lightning can cause harm." + flag-description-custom-monster-damage="Controls whether monsters can deal damage." + flag-description-custom-mushroom-growth="Controls whether mushrooms can grow." + flag-description-custom-mycelium-spread="Controls whether mycelium can spread." + flag-description-custom-pistons="Controls whether pistons can be used." + flag-description-custom-portal-use="Controls whether portals can be used." + flag-description-custom-pvp="Controls whether PvP combat is allowed." + flag-description-custom-ride="Controls whether vehicles(including animals) can be mounted." + flag-description-custom-sleep="Controls whether players can sleep in beds." + flag-description-custom-snow-fall="Controls whether snow can fall." + flag-description-custom-snow-melt="Controls whether snow can melt." + flag-description-custom-snowman-trail="Controls whether snowmen can create snow beneath them." + flag-description-custom-soil-dry="Controls whether soil will dry." + flag-description-custom-spawn-ambient="Controls whether ambients, such as bats, can spawn." + flag-description-custom-spawn-animal="Controls whether animals, such as cows and pigs, can spawn." + flag-description-custom-spawn-aquatic="Controls whether aquatics, such as squids and guardians, can spawn." + flag-description-custom-spawn-monster="Controls whether monsters, such as creepers and skeletons, can spawn." + flag-description-custom-teleport-from="Controls whether players can teleport from this claim." + flag-description-custom-teleport-to="Controls whether players can teleport to this claim." + flag-description-custom-use="Controls whether players can use non-inventory blocks in this claim." + flag-description-custom-vehicle-destroy="Controls whether vehicles can be destroyed." + flag-description-custom-vehicle-place="Controls whether vehicles(boats, minecarts, etc.) can be placed." + flag-description-custom-vine-growth="Controls whether vines(and kelp) can grow." + flag-description-custom-water-flow="Controls whether water can flow." + flag-description-custom-wither-damage="Controls whether withers can do damage." + flag-description-enter-claim="Controls whether an entity can enter a claim.\n&dExample&f : To prevent players from entering a claim, enter\n&a/cf enter-claim player false\n&bNote&f : If you want to use this for groups, use the /cfg command." + flag-description-entity-chunk-spawn="Controls whether a saved entity can be spawned during chunk load.\n&dExample&f : To prevent horses from spawning during chunk load, enter\n&a/cf entity-chunk-spawn minecraft:horse false\n&bNote&f : This will remove &cALL&f saved entities when a chunk loads. If a chunk is already loaded, it will take affect after it reloads. Use with extreme caution." + flag-description-entity-damage="Controls whether an entity can be damaged.\n&dExample&f : To prevent animals from being damaged, enter\n&a/cf entity-damage minecraft:animal false." + flag-description-entity-riding="Controls whether an entity can be mounted.\n&dExample&f : To prevent horses from being mounted, enter\n&a/cf entity-riding minecraft:horse false." + flag-description-entity-spawn="Controls whether an entity can be spawned into the world.\n&dExample&f : To prevent pigs from spawning, enter\n&a/cf entity-spawn minecraft:pig false\n&bNote&f : This does not include entity items. See item-spawn flag" + flag-description-entity-teleport-from="Controls whether an entity can teleport from a claim.\n&dExample&f : To prevent players from teleporting from this claim, enter\n&a/cf entity-teleport-from player false\n&bNote&f : If you want to use this for groups, use the /cfg command." + flag-description-entity-teleport-to="Controls whether an entity can teleport to a claim.\n&dExample&f : To prevent players from teleporting to this claim, enter\n&a/cf entity-teleport-to player false\n&bNote&f : If you want to use this for groups, use the /cfg command." + flag-description-exit-claim="Controls whether an entity can exit a claim.\n&dExample&f : To prevent players from exiting this claim, enter\n&a/cf exit-claim player false\n&bNote&f : If you want to use this for groups, use the /cfg command." + flag-description-explosion-block="Controls whether an explosion can damage blocks in a claim.\n&dExample&f : To prevent an explosion from affecting any block, enter\n&a/cf explosion-block any false" + flag-description-explosion-entity="Controls whether an explosion can damage entities in a claim.\n&dExample&f : To prevent an explosion from affecting any entity, enter\n&a/cf explosion-entity any false" + flag-description-interact-block-primary="Controls whether a player can left-click(attack) a block.\n&dExample&f : To prevent players from left-clicking chests, enter\n&a/cf interact-block-primary minecraft:chest false" + flag-description-interact-block-secondary="Controls whether a player can right-click a block.\n&dExample&f : To prevent players from right-clicking(opening) chests, enter\n&a/cf interact-block-secondary minecraft:chest false" + flag-description-interact-entity-primary="Controls whether a player can left-click(attack) an entity.\n&dExample&f : To prevent players from left-clicking cows, enter\n&a/cf interact-entity-primary minecraft:cow false" + flag-description-interact-entity-secondary="Controls whether a player can right-click on an entity.\n&dExample&f : To prevent players from interacting with villagers, enter\n&a/cf interact-entity-secondary minecraft:villager false" + flag-description-interact-inventory="Controls whether a player can right-click(open) a block that contains inventory such as a chest.\n&dExample&f : To prevent players from opening chests, enter\n&a/cf interact-inventory minecraft:chest false" + flag-description-interact-inventory-click="Controls whether a player can click on an inventory slot.\n&dExample&f : To prevent players from clicking on an inventory slot that contains a diamond, enter\n&a/cf interact-inventory-click minecraft:diamond false" + flag-description-interact-item-primary="Controls whether a player can left-click(attack) with an item.\n&dExample&f : To prevent players from left-clicking with a diamond sword, enter\n&a/cf interact-item-primary minecraft:diamond_sword false" + flag-description-interact-item-secondary="Controls whether a player can right-click with an item.\n&dExample&f : To prevent players from right-clicking with flint and steel, enter\n&a/cf interact-item-secondary minecraft:flint_and_steel false" + flag-description-item-drop="Controls whether an item can be dropped in a claim.\n&dExample&f : To prevent diamond's from being dropped by players in this claim, enter\n&a/cf item-drop minecraft:flint_and_steel false context[source=player]" + flag-description-item-pickup="Controls whether an item can be picked up in a claim.\n&dExample&f : To prevent diamond's from being picked up by players, enter\n&a/cf item-pickup minecraft:diamond false" + flag-description-item-spawn="Controls whether an item can be spawned in a claim.\n&dExample&f : To prevent feather's from being spawned in this claim, enter\n&a/cf item-spawn minecraft:feather false" + flag-description-item-use="Controls whether an item can be used.\n&dExample&f : To prevent apples from being eaten in this claim, enter\n&a/cf item-use minecraft:apple false" + flag-description-leaf-decay="Controls whether leaves can decay in a claim.\n&dExample&f : To prevent leaves from decaying in this claim, enter\n&a/cf leaf-decay any false" + flag-description-liquid-flow="Controls whether liquid, such as lava and water, is allowed to flow in a claim.\n&dExample&f : To prevent any type of liquid flow in this claim, enter\n&a/cf liquid-flow any false" + flag-description-portal-use="Controls whether a portal can be used.\n&dExample&f : To prevent only players from using portal without affecting non-players, enter\n&a/cf portal-use any false context[source=player]" + flag-description-projectile-impact-block="Controls whether a projectile can impact(collide) with a block.\n&dExample&f : To prevent pixelmon pokeball's from impacting blocks, enter\n&a/cf projectile-impact-block any false[source=pixelmon:occupiedpokeball]\n&bNote&f : This involves things such as potions, arrows, throwables, pixelmon pokeballs, etc." + flag-description-projectile-impact-entity="Controls whether a projectile can impact(collide) with an entity.\n&dExample&f : To prevent arrows shot by players from impacting entities, enter\n&a/cf projectile-impact-entity minecraft:arrow false[source=player]\n&bNote&f : This involves things such as potions, arrows, throwables, pixelmon pokeballs, etc." + flag-invalid-context="&cInvalid context '&f{context}&c' entered for base flag &f{flag}&c." + flag-invalid-meta="&cInvalid target meta '&f{value}&c' entered for base flag &f{flag}&c." + flag-invalid-target="&cInvalid target '&f{target}&c' entered for base flag &f{flag}&c." + flag-not-found="&cFlag {flag}&c not found." + flag-not-set="{flag}&f is currently not set.\nThe default claim value of {value}&f will be active until set." + flag-overridden="&cFailed to set claim flag. The flag &f{flag}&c has been overridden by an admin." + flag-override-not-supported="&cClaim type {type}&c does not support flag overrides." + flag-reset-success="&aClaim flags reset to defaults successfully." + flag-reset-warning="&6Are you sure you want to reset this claim's flag data back to default?" + flag-set-permission-target="&aSet {type}&a permission &b{permission}&a to {value}&a with contexts &7{contexts}&a on &6{target}&a." + flag-ui-click-allow="Click here to allow this flag." + flag-ui-click-deny="Click here to deny this flag." + flag-ui-click-remove="Click here to remove this flag." + flag-ui-click-toggle="Click here to toggle {flag}&f value." + flag-ui-info-claim="Claim is checked before default values. Allows claim owners to specify flag settings in claim only." + flag-ui-info-default="Default is last to be checked. Both claim and override take priority over this." + flag-ui-info-inherit="Inherit is an enforced flag set by a parent claim that cannot changed." + flag-ui-info-override="Override has highest priority and is checked above both default and claim values. Allows admins to override all basic and admin claims." + flag-ui-inherit-parent="This flag is inherited from parent claim {name}&f and &ncannot&f be changed." + flag-ui-override-no-permission="This flag has been overridden by an administrator and can &n&cNOT&f be changed." + flag-ui-override-permission="{flag}&f is currently being &coverridden&f by an administrator.\nClick here to remove this flag." + flag-ui-return-flags="Return to flags" + label-accessors=Accessors + label-area=Area + label-blocks=Blocks + label-builders=Builders + label-buy=Buy + label-cancel=Cancel + label-children=children + label-confirm=Confirm + label-containers=Containers + label-context=Context + label-created=Created + label-displaying=Displaying + label-expired=Expired + label-farewell=Farewell + label-flag=Flag + label-greeting=Greeting + label-group=Group + label-inherit=Inherit + label-location=Location + label-managers=Managers + label-name=Name + label-no=No + label-output=Output + label-owner=Owner + label-permission=Permission + label-player=Player + label-price=Price + label-raid=Raid + label-resizable=Resizable + label-result=Result + label-schematic=Schematic + label-source=Source + label-spawn=Spawn + label-target=Target + label-trust=Trust + label-type=Type + label-unknown=Unknown + label-user=User + label-world=World + label-yes=Yes + mode-admin="&aAdministrative claims mode active. Any claims created will be free and editable by other administrators." + mode-basic="&aBasic claim creation mode enabled." + mode-nature="&aReady to restore claim! Right click on a block to restore, and use &f/modebasic&c to stop." + mode-subdivision="&aSubdivision creation mode enabled. Use &f/modebasic&a to exit." + mode-town="&aTown creation mode enabled." + option-description-abandon-delay="&aThe amount of days before a newly created claim can be abandoned." + option-description-abandon-return-ratio="&aThe portion of basic claim blocks returned to a player when a claim is abandoned." + option-description-blocks-accrued-per-hour="&aBlocks earned per hour.\n&dNote&f: See /playerinfo for more information." + option-description-chest-expiration="&aNumber of days of inactivity before an automatic chest claim will expire.\n&dNote&f: On expiration, a claim may either be restored back to original state or be deleted. This depends on the server configuration. Contact an administrator for more information." + option-description-create-limit="&aMaximum number of basic claims per player.\n&dNote&f: Setting a value under 0 will make it unlimited." + option-description-create-mode="&aThe claim create mode type (Area = 2D, Volume = 3D).\n&dNote&f: &bArea&a only affects x and z axis.\n&bVolume&a affects x, y, and z axis." + option-description-economy-block-cost="&aThe economy amount to charge per block of a claim.\n&dNote&f: The formula to calculate price is amount * total claim blocks." + option-description-economy-block-sell-return="&aThe return ration for selling claim blocks.\n&dNote&f: The formula to calculate return is ratio * total claim blocks." + option-description-expiration="&aNumber of days of inactivity before a claim will expire.\n&dNote&f: On expiration, a claim may either be restored back to original state or be deleted. This depends on the server configuration. Contact an administrator for more information." + option-description-initial-blocks="&aThe number of claim blocks a player has initially, by default." + option-description-max-accrued-blocks="&aThe limit on accrued blocks (over time).\n&dNote&f: This doesn't limit purchased or admin-gifted blocks." + option-description-max-level="&aThe maximum level, on y-axis, that a claim can be created in." + option-description-max-size-x="&aThe max size in blocks that the x-axis can be." + option-description-max-size-y="&aThe max size in blocks that the y-axis can be." + option-description-max-size-z="&aThe max size in blocks that the z-axis can be." + option-description-min-level="&aThe minimum level, on y-axis, that a claim can be created in." + option-description-min-size-x="&aThe min size in blocks that the x-axis can be." + option-description-min-size-y="&aThe min size in blocks that the y-axis can be." + option-description-min-size-z="&aThe min size in blocks that the z-axis can be." + option-description-player-command="&aUsed for executing a command with specific contexts." + option-description-player-deny-flight="&aUsed to determine if a player is unable to fly in a claim.\n&dNote&f: This does not give players the ability to fly, it merely removes the ability if set. This provides the greatest compatibility with plugins." + option-description-player-deny-godmode="&aUsed to determine if a player can be in godmode within a claim.\n&dNote&f: This does not give players the ability to be in godmode, it merely removes the ability if set. This provides the greatest compatibility with plugins." + option-description-player-deny-hunger="&aUsed to if a player's hunger is denied in a claim.\n&dNote&f: This does not give players the ability to gain hunger, it merely removes the ability to cause hunger if set. This provides the greatest compatibility with plugins." + option-description-player-gamemode="&aUsed to determine the gamemode of a player in a claim." + option-description-player-health-regen="&aUsed to set the health regen amount for a player in a claim.\n&dNote&f: If the player is at max health, this will have no effect. \n&dNote&f: A value of &6-1&f disables this option." + option-description-player-keep-inventory="&aUsed to determine if a player can keep inventory after death in a claim." + option-description-player-keep-level="&aUsed to determine if a player can keep their level after death in a claim." + option-description-player-walk-speed="&aUsed to set a player's walk speed in a claim.\n&dNote&f: A value of &6-1&f disables this option." + option-description-player-weather="&aUsed to set a player's weather in a claim." + option-description-radius-inspect="&aThe radius in blocks used to search for nearby claims while inspecting." + option-description-radius-list="&aThe radius in blocks used to list nearby claims." + option-description-tax-expiration="&aNumber of days after not paying taxes before a claim will be frozen.\n&dNote&f: A frozen state means you will have no access to build or interact in claim until taxes are paid." + option-description-tax-expiration-days-keep="&aNumber of days to keep a basic claim after frozen and before expiring.\n&dNote&f: On expiration, a claim may either be restored back to original state or be deleted. This depends on the server configuration. Contact an administrator for more information." + option-description-tax-rate="&aThe tax rate of claim.\n&dNote&f: Tax rate is calculated by the number of claimblocks in the basic claim." + option-invalid-context="&cInvalid context '&f{context}&c' entered for option &f{option}&c." + option-invalid-target="&cInvalid target '&f{target}&c' entered for option &f{option}&c." + option-invalid-value="&cInvalid value '&6{value}&c' entered for option &f{option}&c.\n&dNote&f: This option only accepts &f{type}&c values." + option-not-found="&cOption {option}&c not found." + option-not-set="{option}&f is currently not set.\n&dNote&f: The default option value of {value}&f will be active until set." + option-override-not-supported="&cClaim type {type}&c does not support option overrides." + option-player-deny-flight="&cYou do not have access to fly in this claim and have been teleported to a safe spot on ground." + option-reset-success="&aClaim options reset to defaults successfully." + option-set-target="&aSet {type}&a option &b{option}&a to {value}&a with contexts &7{contexts}&a on &6{target}&a." + option-ui-click-toggle="Click here to toggle {option}&f value." + option-ui-inherit-parent="This option is inherited from parent claim {name}&f and &ncannot&f be changed." + option-ui-overridden="&cFailed to set option. The option &f{option}&c has been overridden by an admin." + option-ui-override-no-permission="This option has been overridden by an administrator and can &n&cNOT&f be changed." + owner-admin="an administrator" + permission-access="&cYou don't have &6{player}&c's permission to access that." + permission-assign-without-having="&cYou are not allowed to assign a permission that you do not have." + permission-ban-block="&cThe block {id}&c has been &l&nBANNED&c and cannot be used." + permission-ban-entity="&cThe entity {id}&c has been &l&nBANNED&c and cannot be used." + permission-ban-item="&cThe item {id}&c has been &l&nBANNED&c and cannot be used." + permission-build="&cYou don't have &6{player}&c's permission to build." + permission-build-near-claim="&cYou don't have &6{player}&c's permission to build near claim." + permission-claim-create="&cYou don't have permission to claim land." + permission-claim-delete="&cYou don't have permission to delete {type}&c claims." + permission-claim-enter="&cYou don't have permission to enter this claim." + permission-claim-exit="&cYou don't have permission to exit this claim." + permission-claim-ignore="&cYou do not have permission to ignore {type}&c claims." + permission-claim-list="&cYou don't have permission to get information about another player's land claims." + permission-claim-manage="&cYou don't have permission to manage {type}&c claims." + permission-claim-reset-flags="&cYou don't have permission to reset {type}&c claims to flag defaults." + permission-claim-reset-flags-self="&cYou don't have permission to reset your claim flags to defaults." + permission-claim-resize="&cYou don't have permission to resize this claim." + permission-claim-sale="&cYou don't have permission to sell this claim." + permission-claim-transfer-admin="&cYou don't have permission to transfer admin claims." + permission-clear="&cCleared permissions in this claim. To set permission for ALL your claims, stand outside them." + permission-clear-all="&cOnly the claim owner can clear all permissions." + permission-command-trust="&cYou don't have permission to use this type of trust." + permission-cuboid="&cYou don't have permission to create/resize basic claims in 3D mode." + permission-edit-claim="&cYou don't have permission to edit this claim." + permission-fire-spread="&cYou don't have permission to spread fire in this claim." + permission-flag-defaults="&cYou don't have permission to manage flag defaults." + permission-flag-overrides="&cYou don't have permission to manage flag overrides." + permission-flag-use="&cYou don't have permission to use this flag." + permission-flow-liquid="&cYou don't have permission to flow liquid in this claim." + permission-global-option="&cYou don't have permission to manage global options." + permission-grant="&cYou can't grant a permission you don't have yourself." + permission-group-option="&cYou don't have permission to assign an option to a group." + permission-interact-block="&cYou don't have &6{player}'s &cpermission to interact with the block &d{block}&c." + permission-interact-entity="&cYou don't have &6{player}'s &cpermission to interact with the entity &d{entity}&c." + permission-interact-item="&cYou don't have &6{player}'s &cpermission to interact with the item &d{item}&c." + permission-interact-item-block="&cYou don't have permission to use &d{item}&c on a &b{block}&c." + permission-interact-item-entity="&cYou don't have permission to use &d{item}&c on a &b{entity}&c." + permission-inventory-open="&cYou don't have &6{player}'s&c permission to open &d{block}&c." + permission-item-drop="&cYou don't have &6{player}'s&c permission to drop the item &d{item}&c in this claim." + permission-item-use="&cYou can't use the item &d{item}&c in this claim." + permission-option-defaults="&cYou don't have permission to manage option defaults." + permission-option-overrides="&cYou don't have permission to manage option overrides." + permission-option-use="&cYou don't have permission to use this option." + permission-override-deny="&cThe action you are attempting to perform has been denied by an administrator's override flag." + permission-player-admin-flags="&cYou don't have permission to change flags on an admin player." + permission-player-option="&cYou don't have permission to assign an option to a player." + permission-player-view-others="&cYou don't have permission to view other players." + permission-portal-enter="&cYou can't use this portal because you don't have &6{player}'s &cpermission to enter the destination claim." + permission-portal-exit="&cYou can't use this portal because you don't have &6{player}'s &cpermission to exit the destination claim." + permission-protected-portal="&cYou don't have permission to use portals in this claim owned by &6{player}'s&c." + permission-trust="&cYou don't have &6{player}'s&c permission to manage permissions here." + permission-visual-claims-nearby="&cYou don't have permission to visualize nearby claims." + player-accrued-blocks-exceeded="&cPlayer &6{player}&c has a total of &6{total}&c and will exceed the maximum allowed accrued claim blocks if granted an additional &6{amount}&c of blocks.\nEither lower the amount or have an admin grant the user with an override." + player-remaining-blocks-2d="&aYou may claim up to &6{block-amount}&a more blocks." + player-remaining-blocks-3d="&aYou may claim up to &6{chunk-amount}&a more chunks. &f({block-amount})" + playerinfo-ui-abandon-return-ratio="&eAbandoned Return Ratio&f : &a{ratio}" + playerinfo-ui-block-accrued="&eAccrued Blocks&f : &a{amount}&7(&d{block_amount}&f per hour&7)" + playerinfo-ui-block-bonus="&eBonus Blocks&f : &a{amount}" + playerinfo-ui-block-initial="&eInitial Blocks&f : &a{amount}" + playerinfo-ui-block-max-accrued="&eMax Accrued Blocks&f : &a{amount}" + playerinfo-ui-block-remaining="&eRemaining Blocks&f : &a{amount}" + playerinfo-ui-block-total="&eTotal Blocks&f : &a{amount}" + playerinfo-ui-chunk-total="&eTotal Claimable Chunks&f : &a{amount}" + playerinfo-ui-claim-level="&eMin/Max Claim Level&f : &a{level}" + playerinfo-ui-claim-size-limit="&eClaim Size Limits&f : &a{limit}" + playerinfo-ui-claim-total="&eTotal Claims&f : &a{amount}" + playerinfo-ui-economy-block-available-purchase="&eRemaining Blocks for Purchase&f : &a{amount}" + playerinfo-ui-economy-block-cost="&eClaim Block Price&f : &a{amount} per block" + playerinfo-ui-economy-block-sell-return="&eClaim Block Sell Return&f : &a{amount} per block" + playerinfo-ui-last-active="&eLast Active&f : {date}" + playerinfo-ui-tax-current-rate="&eCurrent Claim Tax Rate&f : &a{rate}" + playerinfo-ui-tax-global-claim-rate="&eGlobal Claim Tax Rate&f : &a{rate}" + playerinfo-ui-tax-global-town-rate="&eGlobal Town Tax Rate&f : &a{rate}" + playerinfo-ui-tax-total="&eTotal Tax&f : &a{amount}" + playerinfo-ui-title="&bPlayer Info" + playerinfo-ui-uuid="&eUUID&f : &7{id}" + playerinfo-ui-world="&eWorld&f : &7{name}" + plugin-command-not-found="&cCould not locate the command '&a{command}&c' for plugin &b{id}&c." + plugin-event-cancel="&cA plugin has cancelled this action." + plugin-not-found="&cCould not locate plugin with id &b{id}&c." + plugin-reload="&aGriefDefender has been reloaded." + pvp-claim-not-allowed="&aYou are not allowed to PvP in this claim." + pvp-source-not-allowed="&aYou are not allowed to PvP." + pvp-target-not-allowed="&aYou cannot attack players who are not participating in PvP." + registry-block-not-found="&cThe block {id}&c could not be found in registry." + registry-entity-not-found="&cThe entity {id}&c could not be found in registry." + registry-item-not-found="&cThe item {id}&c could not be found in registry." + resize-overlap="&cCan't resize here because it would overlap another nearby claim." + resize-overlap-subdivision="&cYou can't create a subdivision here because it would overlap an existing subdivision." + resize-same-location="&cYou must select a different block location to resize claim." + resize-start="&aResizing claim. Use your tool again at the new location for this corner." + resize-success-2d="&aClaim resized. You have &6{block-amount} &amore blocks remaining." + resize-success-3d="&aClaim resized. You have &6{chunk-amount} &amore chunks remaining. &f({block-amount})" + result-type-change-deny="&cYou cannot change a claim to {type}." + result-type-change-not-admin="&cYou do not have administrative permissions to change type to {type}&c." + result-type-child-same="{type}&c claims cannot have direct {type}&c children claims." + result-type-create-deny="{type}'s&c cannot be created in the {target_type}." + result-type-no-children="{type}'s&c cannot contain children claims." + result-type-only-subdivision="{type}'s&c can only contain subdivisions." + result-type-requires-owner="&cCould not convert {type} claim to {target_type}. Owner is required." + schematic-abandon-all-restore-warning="&6Are you sure you want to &nabandon&6 &cALL&6 your claims? &cALL DATA WILL BE LOST&f!!&6 Your claims will be restored back to their original state of creation on confirmation." + schematic-abandon-restore-warning="&6Are you sure you want to &nabandon&6 this claim? &cALL DATA WILL BE LOST&f!!&6 This claim will be restored back to its original state of creation on confirmation." + schematic-create="&aCreating schematic backup..." + schematic-create-complete="&aSchematic backup complete." + schematic-create-fail="&cSchematic could not be created." + schematic-deleted="&aSchematic {name} has been deleted." + schematic-none="&aThere are no schematic backups for this claim." + schematic-restore-click="&aClick here to restore claim schematic.\nName: {name}\nCreated: {date}" + schematic-restore-confirmation="&6Are you sure you want to restore? Clicking confirm will restore &cALL&6 claim data with schematic. Use cautiously!" + schematic-restore-confirmed="&aYou have successfully restored your claim from schematic backup &b{name}&a." + spawn-not-set="&cNo claim spawn has been set." + spawn-set-success="&aSuccessfully set claim spawn to &b{location}&a." + spawn-teleport="&aTeleported to claim spawn at &b{location}&a." + tax-claim-expired="&cThis claim has been frozen due to unpaid taxes. The current amount owed is '&a{amount}&c'.\nThere are '&a{days}&c' days left to deposit payment to claim bank in order to unfreeze this claim.\nFailure to pay this debt will result in deletion of claim.\nNote: To deposit funds to claimbank, use &f/claimbank&c deposit <amount>." + tax-claim-paid-balance="&aThe tax debt of '&6{amount}&a' has been paid. Your claim has been unfrozen and is now available for use." + tax-claim-paid-partial="&aThe tax debt of '&6{amount}&a' has been partially paid. In order to unfreeze your claim, the remaining tax owed balance of '&6{balance}&a' must be paid." + tax-info="&aYour next scheduled tax payment of &6{amount}&a will be withdrawn from your account on &b{date}&a." + tax-past-due="&cYou currently have a past due tax balance of &a{balance}&c that must be paid by &b{date}&c. Failure to pay off your tax balance will result in losing your property." + teleport-confirm="&aAre you sure you want to teleport to {pos}? Click confirm to proceed." + teleport-delay-notice="&aYou will teleport in {delay}&a seconds." + teleport-move-cancel="&cTeleport cancelled! You cannot move while attempting to teleport." + teleport-no-safe-location="&cNo safe location found in claim to teleport!\n&aUse the '&f/claiminfo&a' command to set a safe spawn point instead." + teleport-success="&aYou have been teleported to {name}&a." + title-accessor=ACCESSOR + title-all=ALL + title-builder=BUILDER + title-claim=CLAIM + title-container=CONTAINER + title-default=DEFAULT + title-inherit=INHERIT + title-manager=MANAGER + title-override=OVERRIDE + title-own=OWN + tool-not-equipped="&cYou do not have {tool}&c equipped." + town-chat-disabled="&aTown chat disabled." + town-chat-enabled="&aTown chat enabled." + town-create-not-enough-funds="&cYou do not have enough funds to create this town for &a{amount}&c. You currently have a balance of &a{balance}&c and need &a{amount-needed}&c more for creation." + town-name="&aSet town name to {name}&a." + town-not-found="&cTown not found." + town-not-in="&cYou are not in a town." + town-owner="&cThat belongs to the town." + town-tag="&aSet town tag to {tag}." + town-tag-clear="&aThe town tag has been cleared." + town-tax-no-claims="&cYou must own property in this town in order to be taxed." + trust-already-has="&c{target} already has {type}&c permission." + trust-click-show-list="Click here to show list of all players and groups trusted in claim." + trust-grant="&aGranted &6{target}&a permission to {type}&a in current claim." + trust-individual-all-claims="&aGranted &6{player}'s&a full trust to all your claims. To unset permissions for ALL your claims, use &f/untrustall&a." + trust-invalid="&cInvalid trust type entered.\nThe allowed types are : accessor, builder, container, and manager." + trust-list-header="Explicit permissions here:" + trust-no-claims="&cYou have no claims to trust." + trust-plugin-cancel="&cCould not trust {target}&c. A plugin has denied it." + trust-self="&cYou cannot trust yourself." + tutorial-claim-basic="&eClick for Land Claim Help: &ahttp://bit.ly/mcgpuser" + ui-click-confirm="Click to confirm." + ui-click-filter-type="Click here to filter by {type}&f." + untrust-individual-all-claims="&aRevoked &6{target}'s&a access to ALL your claims. To set permissions for a single claim, stand inside it and use &f/untrust&a." + untrust-individual-single-claim="&aRevoked &6{target}'s&a access to this claim. To unset permissions for ALL your claims, use &f/untrustall&a." + untrust-no-claims="&cYou have no claims to untrust." + untrust-owner="&6{owner}&a is owner of claim and cannot be untrusted." + untrust-self="&cYou cannot untrust yourself." + } +} diff --git a/sponge/src/main/resources/assets/lang/fr_FR.conf b/sponge/src/main/resources/assets/lang/fr_FR.conf new file mode 100644 index 0000000..2863cf2 --- /dev/null +++ b/sponge/src/main/resources/assets/lang/fr_FR.conf @@ -0,0 +1,637 @@ +GriefDefender { + descriptions { + abandon-all="Abandonne l'ENSEMBLE de tes terrains." + abandon-claim="Abandonne un terrain." + abandon-top="Abandonne le terrain de plus haut niveau." + buy-blocks="Achète des blocs supplémentaires avec l'argent du serveur.\nNote: Nécessite un plug-in d'économie." + callback="Exécute un rappel défini comme faisant partie d'un objet textuel. Utilisée principalement pour un usage interne." + claim-bank="Utilisée pour retirer ou déposer de l'argent pour une utilisation dans le terrain." + claim-clear="Permet de nettoyer les entités dans un ou plusieurs terrain(s)." + claim-debug="Active/Désactive le mode débug pour les marques du terrain." + claim-farewell="Définis le message de sortie du terrain." + claim-greeting="Définis le message d'accueil du terrain." + claim-ignore="Active/Désactive le mode pour ignorer les terrains." + claim-info="Affiche l'ensemble des informations connues pour le terrain dans lequel tu te trouves." + claim-inherit="Active/Désactive l'héritage des permissions depuis un terrain parent." + claim-list="Liste l'ensemble des terrains connus dans la zone." + claim-name="Définis le nom du terrain." + claim-restore="Restaure le terrain à son état naturel. À utiliser avec prudence." + claim-setspawn="Définis le point d'apparition pour les joueurs." + claim-spawn="Te téléporte au point d'apparition si disponible." + claim-transfer="Transfert un terrain basique ou admin à un autre joueur." + claim-worldedit="Utilise la sélection WorldEdit pour créer un terrain." + cuboid="Active/Désactive le mode terrain cubique." + debug="Capture l'ensemble des actions GD à des fins de débuggage." + delete-all="Supprime l'ensemble des terrains d'un autre joueur." + delete-all-admin="Supprime l'ensemble des terrains admin." + delete-claim="Supprime la protection dans laquelle tu es, même s'il ne t'appartiens pas." + delete-top="Supprime le terrain de plus haut niveau dans lequel tu es, même s'il ne t'appartiens pas." + flag-claim="Récupère/Définis les marques du terrain dans lequel tu es." + flag-group="Récupère/Définis les marques de permission pour un groupe dans le terrain dans lequel tu es." + flag-player="Récupère/Définis les marques de permission pour un joueur dans le terrain dans lequel tu es." + flag-reset="Remets par défaut les marques dans le terrain." + mode-admin="Bascule l'outil pelle en mode terrain admin." + mode-basic="Re-bascule l'outil pelle en mode terrain basique." + mode-nature="Bascule l'outil pelle en mode restauration." + mode-subdivision="Bascule l'outil pelle en mode sous-division, utilisé pour sous-diviser les terrains." + mode-town="Bascule l'outil pelle en mode terrain Village." + option-claim="Récupère/Définis les options dans le terrain dans lequel tu es." + permission-group="Définis une permission sur un groupe avec un contexte de terrain." + permission-player="Définis une permission sur un joueur avec un contexte de terrain." + player-adjust-bonus-blocks="Mets à jour le nombre total de blocs de terrain bonus pour un joueur." + player-info="Affiche les informations concernant un joueur." + player-set-accrued-blocks="Mets à jour le nombre total de blocs gagné par un joueur." + reload="Recharge la configuration des paramètres de GriefDefender." + schematic="Gère les patrons de terrain. Utilises '/claimschematic create <nom>' pour créer directement une copie du terrain." + sell-blocks="Vends tes blocs de terrain contre de l'argent serveur.\nNote: Nécessite un plug-in d'économie." + sell-claim="Mets en vente ton terrain. Utilises /claimsell <montant>.\nNote: Nécessite un plug-in d'économie." + town-chat="Active/Désactive le chat du Village." + town-tag="Définis le blason du village." + trust-group="Donne à un groupe accès à ton terrain.\nAccessor: Permet d'intéragir avec l'ensemble des blocs sans inventaire.\nContainer: Permet d'intéragir avec l'ensemble des blocs avec inventaire.\nBuilder: Permet la même chose qu'au dessus avec en plus la possibilité de placer et casser des blocs.\nManager: Permet la même chose qu'au dessus avec en plus la possibilité de gérer les paramètres de la protection." + trust-group-all="Donne à un groupe l'accès à l'ENSEMBLE de tes terrains.\nAccessor: Permet d'intéragir avec l'ensemble des blocs sans inventaire.\nContainer: Permet d'intéragir avec l'ensemble des blocs avec inventaire.\nBuilder: Permet la même chose qu'au dessus avec en plus la possibilité de placer et casser des blocs.\nManager: Permet la même chose qu'au dessus avec en plus la possibilité de gérer les paramètres de la protection." + trust-player="Donne à un joueur accès à ton terrain.\nAccessor: Permet d'intéragir avec l'ensemble des blocs sans inventaire.\nContainer: Permet d'intéragir avec l'ensemble des blocs avec inventaire.\nBuilder: Permet la même chose qu'au dessus avec en plus la possibilité de placer et casser des blocs.\nManager: Permet la même chose qu'au dessus avec en plus la possibilité de gérer les paramètres de la protection." + trust-player-all="Donne à un joueur accès à l'ENSEMBLE de tes terrains.\nAccessor: Permet d'intéragir avec l'ensemble des blocs sans inventaire.\nContainer: Permet d'intéragir avec l'ensemble des blocs avec inventaire.\nBuilder: Permet la même chose qu'au dessus avec en plus la possibilité de placer et casser des blocs.\nManager: Permet la même chose qu'au dessus avec en plus la possibilité de gérer les paramètres de la protection." + untrust-group="Supprime les accès d'un groupe à ton terrain." + untrust-group-all="Supprime les accès d'un groupe à l'ENSEMBLE de tes terrains." + untrust-player="Supprime les accès d'un joueur à ton terrain." + untrust-player-all="Supprime les accès d'un joueur à l'ENSEMBLE de tes terrains." + version="Affiche les informations sur la version de GriefDefender." + } + messages { + abandon-all-delay-warning="&aCes terrains ont été créés récemment et ne peuvent être abandonnés." + abandon-all-warning="&6Es-tu sûr de vouloir abandonner &cTOUS&6 tes terrains ?" + abandon-claim-delay-warning="&aCe terrain a été créé récemment et ne peut pas être abandonnée avant le &6{date}&a." + abandon-claim-failed="&aN'a pas réussi à abandonner le terrain. Résultat du terrain était: &f{result}&a." + abandon-claim-missing="&cPas de terrain trouvé. Va dans le terrain que tu veux abandonner ou envisages &f/abandonall&c." + abandon-other-success="Le terrain de &6{player}&a a été abandonné. &6{player}&a dispose de maintenant &6{amount}&a blocs de terrain disponibles." + abandon-success="&aTerrain abandonné. Tu as maintenant &6{amount}&a blocs de terrain disponibles." + abandon-top-level="&cCe terrain ne peut être abandonné car il contient un ou plusieurs terrain(s) enfant(s). Pour abandonner un terrain avec enfant, tu dois utiliser &f/abandontop&c à la place." + abandon-town-children="&cTu n'as pas la permission pour abandonner un village contenant des terrains enfants qui ne t'appartiennent pas. Utilises &f/ignoreclaims&c ou fais en sorte que le propriétaire des terrains enfants les abandonne d'abord. Si tu veux abandonner le village sans affecter les terrains enfants, utilises &f/abandon&c à la place." + abandon-warning="&6Es-tu sûr de vouloir abandonner ce terrain ? Il ne sera plus protégé contre les dégâts." + adjust-accrued-blocks-success="&aAjustement du nombre de blocs de terrain gagnés par &6{player}&a de &6{amount}&a. Nouveau total de blocs de terrain gagnés: &6{total}&a." + adjust-bonus-blocks-success="&aAjustement du nombre de blocs de terrain, bonus &6{player}&a de &6{amount}&a. Nouveau total de blocs de terrain bonus: &6{total}&a." + bank-click-view-transactions="Clique ici pour voir les transactions bancaires" + bank-deposit="&aDépôt de &6{amount}&a avec succès dans la banque." + bank-deposit-no-funds="&cTu n'as pas suffisamment de fonds pour faire un dépôt en banque." + bank-info="&aSolde: &6{balance}&a \nTaxe: &6{tax-amount}&f prélevée au &7{time-remaining}&a \nTaxe due: &6{tax-balance}." + bank-no-permission="&cTu n'as pas la permission de gérer la banque de terrain de &6{player}&c." + bank-tax-system-disabled="&cLa banque/système de taxe n'est pas activé. Si tu veux l'activer, paramètre 'bank-tax-system' à true dans le fichier de configuration." + bank-title-transactions="Transactions Bancaire" + bank-withdraw="&aRetrait de &6{amount}&a de la banque avec succès." + bank-withdraw-no-funds="&cLa banque de terrain a un solde de &a{balance}&c et n'a pas suffisamment de fonds pour un retrait de &a{amount}&c." + block-claimed="&aCe bloc a été réclamé par &6{player}&a." + block-not-claimed="&cPersonne n'a réclamé ce bloc." + block-sale-value="&aChaque bloc de terrain vaut &6{amount}&a. Tu as &6{total}&a disponible pour la vente." + claim-above-level="&cImpossible de réclamer le bloc car il est au-dessus de niveau maximum limite de &a{limit}&c." + claim-action-not-available="&cCette action n'est pas disponible dans {type}&c." + claim-automatic-notification="&cCe coffre et les blocs à proximités sont protégés." + claim-below-level="&cImpossible de réclamer le bloc car il est en-dessous du niveau minimum limite de &a{limit}&c." + claim-chest-confirmation="&cCe coffre est protégé." + claim-chest-outside-level="&cCe coffre ne peut pas être protégé car sa position est en dehors du niveau limite de terrain de &a{min-level}&c et &a{max-level}&c." + claim-children-warning="&6Cet terrain contient des terrains enfants. Si tu es sûr de vouloir la supprimer, utilises &f/deleteclaim&6 à nouveau." + claim-context-not-found="&cContexte &f{context}&c non trouvé." + claim-disabled-world="&cLes terrains sont désactivées dans ce monde." + claim-expired-inactivity="&cLe terrain du joueur &6{player} avec l'id &f{uuid}&c a expiré et a été supprimé pour cause d'inactivité." + claim-farewell="&aDéfinis le message de sortie à {farewell}&a." + claim-farewell-clear="&aLe message de sortie a été supprimé." + claim-farewell-invalid="&cLa marque de terrain &f{flag}&c n'est pas valide." + claim-greeting="&aDéfinis le message d'accueil sur {greeting}&a." + claim-greeting-clear="&aLe message de d'accueil a été supprimé." + claim-ignore="&aIgnore maintenant les terrains." + claim-last-active="&aDernière activité du terrain le &6{date}&a." + claim-mode-start="coin {type}&a définis ! Clique droit à nouveau dans le coin opposé pour protéger un rectangle de terrain. Pour annuler, écris &f/claim&a pour sortir du terrain." + claim-name="&aDéfinis le nom du terrain à {name}&a." + claim-no-claims="&cTu n'as aucun terrain reclamé." + claim-no-set-home="&cTu dois avoir la confiance pour utiliser /sethome ici." + claim-not-found="&cIl n'y a pas de terrain ici." + claim-not-yours="&cCe n'est pas ton terrain." + claim-owner-already="&cTu es déjà le propriétaire de ce terrain." + claim-owner-only="&cSeulement &6{player}&c peut modifier ce terrain." + claim-protected-entity="&cCela appartient à &6{player}&c." + claim-respecting="&aRespecte maintenant les terrains." + claim-restore-success="&aRestauration du terrain avec succès." + claim-show-nearby="&aTrouvé &6{amount}&a terrain(s) à proximité." + claim-size-max="&cLa taille de &6{axis}&c sur &a{size}&c du terrain excède la taille maximum de &a{max-size}&c.\nLa zone a besoin d'être un minimum de &a{min-area}&c et de maximum &a{max-area}" + claim-size-min="&cLa taille de &6{axis}&c sur &a{size}&c du terrain est sous la taille minimum de &a{min-size}&c.\nLa zone a besoin d'être un minimum de &a{min-area}&c et de maximum &a{max-area}" + claim-size-need-blocks-2d="&cTu n'as pas suffisamment de blocs pour un terrain de cette taille.\nTu as besoin de &a{block-amount}&c blocs supplémentaires." + claim-size-need-blocks-3d="&cTu n'as pas suffisamment de blocs pour un terrain de cette taille.\nTu as besoin de &a{chunk-amount}&c chunks supplémentaires. &f({block-amount})" + claim-size-too-small="&cLa taille de la zone de terrain sélectionnée de &a{width}&fx&a{length}&c sera trop petite. Une protection doit être au minimum de &a{min-width}&fx&a{min-length}&c en taille." + claim-start="{type}&a Coin défini ! Utilise la pelle au coin opposé pour protéger un rectangle de terre. Pour annuler, met la pelle de côté." + claim-too-far="&cC'est trop loin." + claim-transfer-exceeds-limit="&cLe terrain ne peut pas être transféré car cela dépassera la limite de création du nouveau propriétaire." + claim-transfer-success="&aTerrain transféré." + claim-type-not-found="&cPas de terrain {type}&c trouvé." + claiminfo-ui-admin-settings="Paramètres Admin" + claiminfo-ui-bank-info="Information Bancaire" + claiminfo-ui-claim-expiration="Expiration du terrain" + claiminfo-ui-click-admin="Clique ici pour voir les paramètres admin" + claiminfo-ui-click-bank="Clique ici pour vérifier les informations bancaire" + claiminfo-ui-click-change-claim="Clique ici pour changer le terrain à {type}" + claiminfo-ui-click-toggle="Clique ici pour basculer la valeur" + claiminfo-ui-deny-messages="Messages de refus" + claiminfo-ui-flag-overrides="Marque outrepassant" + claiminfo-ui-for-sale="À Vendre" + claiminfo-ui-inherit-parent="Héritage parent" + claiminfo-ui-last-active="Dernière activité" + claiminfo-ui-north-corners="Coin nord" + claiminfo-ui-pvp-override="Outrepasser PvP" + claiminfo-ui-requires-claim-blocks="Blocs de terrain nécessaires" + claiminfo-ui-return-bankinfo="Retour aux informations bancaires" + claiminfo-ui-return-claiminfo="Retour aux informations de terrain" + claiminfo-ui-return-settings="Retour aux informations standards" + claiminfo-ui-size-restrictions="Restriction de taille" + claiminfo-ui-south-corners="Coin sud" + claiminfo-ui-teleport-direction="Clique ici pour te téléporter au coin {direction}&f du terrain" + claiminfo-ui-teleport-feature="Tu n'as pas la permission pour utiliser la fonction de téléportation dans ce terrain" + claiminfo-ui-teleport-spawn="Clique ici pour te téléporter au point d'appartition du terrain" + claiminfo-ui-title-claiminfo="Information du terrain" + claiminfo-ui-town-settings="Paramètre de la ville" + claimlist-ui-click-info="Clique ici pour voir plus d'informations" + claimlist-ui-click-purchase="Clique ici pour acheter le terrain" + claimlist-ui-click-teleport-target="Clique ici pour te téléporter à {name}&f {target}&f dans &6{world}" + claimlist-ui-click-toggle-value="Clique ici pour basculer la valeur {type}" + claimlist-ui-click-view-children="Clique ici pour lister les terrains enfants" + claimlist-ui-click-view-claims="Clique ici pour voir les terrains qui t'appartiennent" + claimlist-ui-no-claims-found="Pas de terrain trouvé dans ce monde." + claimlist-ui-return-claimlist="Retourne à la liste des terrains" + claimlist-ui-title="Liste les terrains" + claimlist-ui-title-child-claims="Terrains enfants" + command-blocked="&cLa commande &f{command}&c a été bloquée par le propriétaire du terrain &6{player}&c." + command-claimban-success-block="&cBANNISSEMENT&a du bloc avec l'id {id}&a avec succès." + command-claimban-success-entity="&cBANNISSEMENT&a de l'entité avec l'id {id}&a avec succès." + command-claimban-success-item="&cBANNISSEMENT&a de l'objet avec l'id {id}&a avec succès." + command-claimbuy-title="&bTerrain à vendre" + command-claimclear-killed="&c &6{amount}&a entités de type {type}&f ont été tuées." + command-claimclear-no-entities="&cImpossible de localiser une entité de type {type}&c." + command-claimclear-uuid-deny="&cSeulement les admins peuvent nettoyer les terrains par UUID." + command-claimflagdebug-disabled="Mode débug de marque de terrain &cOFF" + command-claimflagdebug-enabled="Mode débug de marque de terrain &aON" + command-claiminfo-not-found="&cPas de joueur valide ou de terrain avec cet UUID trouvé." + command-claiminfo-uuid-required="&cUUID du terrain nécessaire si exécuté depuis une source non joueur." + command-claiminherit-disabled="Héritage terrain parent &cOFF" + command-claiminherit-enabled="Héritage terrain parent &aON" + command-claimmode-disabled="Mode terrain &cOFF" + command-claimmode-enabled="Mode terrain &aON&f\n&aClique gauche pour inspecter.\n&aClique droit pour réclamer.&b\nNote&f: &aUtilise &f/claim&a pour sortir du mode." + command-claimspawn-not-found="&aNe peut pas localiser un terrain avec le nom {name}&a." + command-claimunban-success-block="&cDÉBANISSEMENT&a du bloc avec l'id {id}&a avec succès." + command-claimunban-success-entity="&cDÉBANISSEMENT&a de l'entité avec l'id {id}&a avec succès." + command-claimunban-success-item="&cDÉBANISSEMENT&a de l'objet avec l'id {id}&a avec succès." + command-cuboid-disabled="&aRéclame maintenant en mode &d2D&a." + command-cuboid-enabled="&aRéclame maintenant en mode &d3D&a." + command-execute-failed="&cÉchec de l'exécution de la commande '{command} {args}'" + command-giveblocks-confirmation="&6Es-tu sûr de vouloir donner à {player}&6 {amount}&6 blocs de terrain ?" + command-giveblocks-confirmed="&aTransfert de bloc de terrain complet." + command-giveblocks-not-enough="&cPas suffisamment de blocs de terrain ! Tu as seulement {amount}&c bloc(s) de terrain disponible pour le transfert.\n&bNote&f: Ce montant n'inclut pas le nombre de blocs de terrain initial. Cela inclut uniquement le nombre de blocs gagnés et bonus." + command-giveblocks-received="&aTu as reçu {amount}&a blocs de terrain du joueur {player}&a." + command-inherit-only-child="&cCette commande ne peut être utilisée que dans des terrains enfants." + command-invalid="&cPas de commande valide entrée." + command-invalid-amount="&cMontant invalide &6{amount}&c entré." + command-invalid-claim="&cCette commande ne peut être utilisée dans les terrains de type {type}&c." + command-invalid-group="&cGroupe &6{group}&c n'est pas valide." + command-invalid-player="&cJoueur &6{player}&c n'est pas valide." + command-invalid-player-group="&cPas un joueur ou groupe valide." + command-invalid-type="&cType {type}&c invalide spécifié." + command-not-available-economy="&cCette commande n'est pas disponible quand le serveur est en mode économie." + command-option-exceeds-admin="&cLa valeur &a'{value}&c' de l'option dépasse la valeur admin de '&a{admin-value}&c'. Ajustement à la valeur admin..." + command-pet-confirmation="&aAnimal transféré." + command-pet-invalid="&cL'animal de type {type} n'est pas actuellement transférable." + command-pet-transfer-cancel="&aTransfert animal annulé." + command-pet-transfer-ready="&aPrêt pour le transfert! Fait un clique droit sur l'animal que tu veux donner, ou annule avec un clique gauche." + command-player-not-found="&cJoueur '&6{player}&c' introuvable." + command-world-not-found="&cMonde '&6{world}&c' introuvable." + command-worldedit-missing="&cCette commande a besoin que WorldEdit soit installé sur le serveur." + create-cancel="&cLa création de ce terrain a été annulé." + create-cuboid-disabled="&cLa création de terrain en mode &d3D&c a été désactivé par un administrateur.\nTu peux uniquement créer un terrain en mode &d3D&c en tant qu'Admin ou dans un terrain en mode &d2D&c qui t'appartient." + create-failed-claim-limit="&cTu as atteint la limite de &a{limit}&c pour les terrains de type {type}&c. Utilises &f/abandon&c pour en supprimer un avant d'en créer un nouveau." + create-failed-result="&aLa création du terrain a échoué à cause de : &6{reason}&a." + create-insufficient-blocks-2d="&cTu n'as pas suffisamment de blocs pour protéger cette zone.\nTu as besoin de &a{amount}&c blocs supplémentaires." + create-insufficient-blocks-3d="&cTu n'as pas suffisamment de blocs pour protéger cette zone.\nTu as besoin de &a{amount}&c chunks supplémentaires. &f({block-amount})" + create-overlap="&cTu ne peut pas créer un terrain ici car il chevaucherait ton autre terrain. Utilises &f/abandonclaim&c pour le supprimer ou utilise la pelle dans un coin pour le redimensionner." + create-overlap-player="&cTu ne peut pas créer un terrain ici car il chevaucherait le terrain de &6{player}&c." + create-overlap-short="&cPas de terrain existant sur le coin sélectionné. Cliques sur un bloc valide à l'intérieur d'un terrain parent afin de créer une sous-division." + create-subdivision-fail="&cPas de terrain existant au coin sélectionné. Cliques sur un bloc valide dans une zone à l'intérieur du terrain parent pour créer la sous-division." + create-subdivision-only="&cImpossible de créer le terrain. Seulement les sous-divisions peuvent être créées à l'endroit d'un bloc unique." + create-success="{type}&a créée ! Utilises &f/trust&a pour la partager avec tes amis." + debug-error-upload="&cErreur d'envoi du contenu {content}&c." + debug-no-records="&cPas d'enregistrement de débug à coller !" + debug-paste-success="&aCollage avec succès !" + debug-record-end="Fin d'enregistrement" + debug-record-start="Démarrage d'enregistrement" + debug-time-elapsed="Temps passé" + delete-all-player-failed="&aNe peut pas supprimer l'ENSEMBLE des terrains de &6{player}&a. Résultat de terrain était &f{result}&a." + delete-all-player-success="&aSuppression de l'ENSEMBLE des terrains de &6{player}&a avec succès." + delete-all-player-warning="&6Es-tu sûr de vouloir supprimer l'ENSEMBLE des terrains de &6{player}&6 ?" + delete-all-type-deny="&cImpossible de supprimer l'ensemble des terrains de type {type}&c. Un plug-in l'a refusé." + delete-all-type-success="&&cSuppression de l'ensemble des terrains {type}&c." + delete-all-type-warning="&6Es-tu sûr de vouloir supprimer l'ENSEMBLE des terrains de type {type}&6 ?" + delete-claim-failed="&aNe peut pas supprimer le terrain. Le résultat du terrain était : &f{result}&a." + delete-claim-success="&aSuppression des terrains de {player}&a." + delete-claim-warning="&6Es-tu sûr de vouloir supprimer les terrains de {player}&6 ?" + economy-balance="&aVotre nouveau solde est de &6{balance}&a." + economy-block-available-purchase-2d="&aTu as suffisamment de fonds pour créer un terrain jusqu'à &6{block-amount} &ablocs supplémentaires." + economy-block-available-purchase-3d="&aTu as suffisamment de fonds pour créer un terrain jusqu'à &6{chunk-amount} &achunks supplémentaires. &f({block-amount})" + economy-block-buy-invalid="&cLe nombre de blocs doit être supérieur à 0." + economy-block-buy-sell-disabled="&cDésolé, l'achat et la vente de blocs de terrain est désactivé." + economy-block-cost="&aChaque bloc de terrain coûte &6{amount}&a." + economy-block-not-available="&cTu n'as pas autant de blocs disponible pour la vente." + economy-block-only-buy="&cLes blocs de terrain ne peuvent qu'être achetés, pas vendus." + economy-block-only-sell="&cLes blocs de terrain ne peuvent qu'être vendus, pas achetés." + economy-block-purchase-confirmation="&aRetrait de &6{amount}&a depuis ton compte. Tu as maintenant &6{balance}&a blocs de terrain disponibles." + economy-block-purchase-cost="&aChaque bloc de terrain coûte &6{amount}&a. Ton solde est de &6{balance}&a." + economy-block-purchase-limit="&cLe nouveau nombre de bloc de terrain total de &a{total}&c va dépasser la limite de bloc maximum de &a{limit}&c. La transaction a été annulée." + economy-block-sale-confirmation="&aDéposé &6{deposit}&a sur ton compte. Tu as maintenant &6{amount}&a blocs de terrain disponibles." + economy-block-sell-error="&cImpossible de vendre les blocs. Raison: &f{reason}&c." + economy-claim-abandon-success="&Terrain abandonné. Tu as été remboursé d'un total de '&6{amount}&a'." + economy-claim-buy-cancelled="&cAchat annulé ! Impossible d'acheter le terrain de &6{player}&c. Resultat est &a{result}" + economy-claim-buy-confirmation="&6Es-tu sûr de vouloir acheter ce terrain pour &a{amount}&6 ? Clique confirm pour procéder." + economy-claim-buy-confirmed="&aTu as acheté le terrain avec succès pour un montant de &6{amount}&a." + economy-claim-buy-not-enough-funds="&cTu n'as pas suffisamment de fond pour acheter ce terrain pour &a{amount}&c. Tu as actuellement un solde de &a{balance}&c et tu as besoin de &a{amount_required}&c supplémentaire pour l'achat" + economy-claim-buy-transfer-cancelled="&cTransfert de terrain annulé ! Ne peut transférer du propriétaire &6{owner}&c à &6{player}&c. Resultat est &a{result}" + economy-claim-not-for-sale="&cCe terrain n'est pas à vendre." + economy-claim-sale-cancelled="&aTu as annulé la vente de ton terrain." + economy-claim-sale-confirmation="&6Es-tu sûr de vouloir vendre ton terrain pour &a{amount}&6 ? Si la protection est vendue, l'ensemble des objets et blocs vont être transférés à l'acheteur. Clique confirm pour valider." + economy-claim-sale-confirmed="&aTu as mis en vente ton terrain avec succès pour un montant de &6{amount}&a." + economy-claim-sale-invalid-price="&cLe prix de vente de &a{amount}&c doit être supérieur ou égal à &a0&c." + economy-claim-sold="&aTon terrain est vendu ! Un montant de &6{amount}&a a été déposé sur ton compte. Ton solde total est maintenant de &6{balance}&a." + economy-mode-block-sale-confirmation="&aDépôt de &6{deposit}&a sur votre compte. Votre solde total est de &6{balance}&a. Tu as maintenant suffisamment de fonds pour protéger jusqu'à &6{amount}&a blocs supplémentaires." + economy-mode-resize-success-2d="&Terrain redimensionné. Ton nouveau solde est de &6{balance}&a. Tu as maintenant suffisamment de fonds pour protéger jusqu'à &6{block-amount} &ablocs supplémentaires." + economy-mode-resize-success-3d="&Terrain redimensionné. Ton nouveau solde est de &6{balance}&a. Tu as maintenant suffisamment de fonds pour protéger jusqu'à &6{chunk-amount} &achunks supplémentaires. &f({block-amount})" + economy-not-enough-funds="&cTu n'as pas suffisamment de fonds pour acheter ce terrain. Ton solde actuel est de '&a{balance}&c' mais tu as besoin de '&a{amount}&c' pour valider l'achat." + economy-not-installed="&cPlug-in d'économie non installé !" + economy-player-not-found="&cPas de compte bancaire trouvé pour le joueur &6{player}&c." + economy-remaining-funds="&aTu as &6{amount}&a de disponible pour protéger du terrain." + economy-virtual-not-supported="&cLe plug-in d'économie ne supporte pas les comptes virtuels, ce qui est nécessaire. Utilises un autre plug-in d'économie ou contact le dev du plug-in pour qu'il supporte les comptes virtuels." + economy-withdraw-error="&cImpossible de retirer des fonds. Raison: &f{reason}&c." + feature-not-available="&cCette fonctionnalité est actuellement en cours de développement et sera disponible dans une version future." + flag-description-block-break="Contrôle si un bloc peut être cassé.\n&dExemple&f : Pour prévenir n'importe quel source de casser un bloc de terre, entre\n&a/cf block-break minecraft:dirt false\n&bNote&f : minecraft représente le modID et dirt représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-block-grow="Contrôle si un bloc peut pousser.\n&dExemple&f : Pour prévenir un cactus de pousser, entre\n&a/cf block-grow minecraft:cactus false\n&bNote&f : minecraft représente le modID et cactus représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-block-modify="Contrôle si un bloc peut être modifié.\n&dExemple&f : Pour prévenir n'importe quelle source d'allumer un bloc, entre\n&a/cf block-modify minecraft:fire false\n&bNote&f : minecraft représente le modID et fire représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-block-place="Contrôle si un bloc peut être placé.\n&dExemple&f : Pour prévenir n'importe quelle source de placer un bloc de terre, entre\n&a/cf block-place minecraft:dirt false\n&bNote&f : minecraft représente le modID et dirt représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-block-spread="Contrôle si un bloc peut s'étendre à un autre.\n&dExemple&f : Pour prévenir le feu de s'étendre, entre\n&a/cf block-spread any false context[source=fire]\n&bNote&f : 'any' réprésente n'importe quel bloc cible et fire représente le blockID sourced.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-collide-block="Contrôle si une entité peut entrer en collision avec un bloc.\n&dExemple&f : Pour prévenir une entité d'entrer en collision avec une plaque de pression en pierre, entre\n&a/cf collide-block minecraft:stone_pressure_plate false\n&bNote&f : minecraft représente le modID et stone_pressure_plate représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-collide-entity="Contrôle si une entité peut entrer en collision avec une entité.\n&dExemple&f : Pour prévenir une entité d'entrer en collision avec un cadre, entre\n&a/cf collide-entity minecraft:item_frame false\n&bNote&f : minecraft représente le modID et item_frame représente le blockID.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-command-execute="Contrôle si une commande peut être exécutée.\n&dExemple&f : Pour prévenir la commandes pixelmon '/shop select' d'être exécutée, entre\n&a/cf command-execute pixelmon:shop[select] false\n&bNote&f : &o&6pixelmon&f représente le modID et &o&6shop&f représente la commande de base, et &o&6select&f représente l'argument.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-command-execute-pvp="Contrôle si une commande peut être exécutée en état PvP.\n&dExemple&f : Pour prévenir la commandes pixelmon '/shop select' d'être exécutée, entre\n&a/cf command-execute pixelmon:shop[select] false\n&bNote&f : &o&6pixelmon&f représente le modID et &o&6shop&f représente la commande de base, et &o&6select&f représente l'argument.\nNe pas spécifier de modID prendra toujours minecraft par défaut." + flag-description-custom-block-break="Contrôle si un bloc peut être cassé." + flag-description-custom-block-grow="Contrôle si un bloc peut pousser." + flag-description-custom-block-place="Contrôle si un bloc peut être placé." + flag-description-custom-block-spread="Contrôle si un bloc peut s'étendre." + flag-description-custom-build="Contrôle les actions autorisées contre les blocs comme le minage, placement et intéraction." + flag-description-custom-chest-access="Contrôle si un joueur peut accéder à l'inventaire d'un coffre." + flag-description-custom-chorus-fruit-teleport="Contrôle si un joueur peut se téléporter en utilisant un fruit chorus." + flag-description-custom-crop-growth="Contrôle si les pousses peuvent grandir." + flag-description-custom-damage-animals="Contrôle si les animaux peuvent prendre des dégâts." + flag-description-custom-enderman-grief="Contrôle si les enderman peuvent grief." + flag-description-custom-enderpearl="Contrôle si une enderpearl peut être utilisée." + flag-description-custom-enter-player="Contrôle si un joueur peut entrer dans une protection." + flag-description-custom-exit-player="Contrôle si un joueur peut sortir de la protection." + flag-description-custom-exp-drop="Contrôle si les orbes d'expériences peuvent apparaîtres." + flag-description-custom-explosion-block="Contrôle si les explosions affectent les blocs." + flag-description-custom-explosion-creeper="Contrôle si un creeper peut exploser." + flag-description-custom-explosion-entity="Contrôle si les explosions affectent les entités." + flag-description-custom-explosion-tnt="Contrôle si une TnT peut exploser." + flag-description-custom-fall-damage="Contrôle si le joueur peut prendre des dégâts de chute." + flag-description-custom-fire-damage="Contrôle si le feu fait des dégâts." + flag-description-custom-fire-spread="Contrôle si le feu peut se répandre." + flag-description-custom-grass-growth="Contrôle si l'herbe peut pousser." + flag-description-custom-ice-form="Contrôle si la glace peut se former." + flag-description-custom-ice-melt="Contrôle si la glace peut fondre." + flag-description-custom-interact-block="Contrôle si un joueur peut intéragir avec les blocs.\n&bNote&f: Cela n'inclut PAS les blocs avec inventaire comme les coffres." + flag-description-custom-interact-entity="Contrôle si un joueur peut intéragir avec une entité.\n&bNote&f: Cela n'inclut PAS l'accès au coffre des entités comme les chevaux." + flag-description-custom-interact-inventory="Contrôle si un joueur peut intéragir avec un inventaire." + flag-description-custom-invincible="Contrôle si un joueur est invincible contre les dégâts." + flag-description-custom-item-drop="Contrôle si un joueur peut jeter un objet." + flag-description-custom-item-pickup="Contrôle si un joueur peut ramasser un objet." + flag-description-custom-lava-flow="Contrôle si la lave peut couler." + flag-description-custom-leaf-decay="Contrôle si les feuilles peuvent dépérir." + flag-description-custom-lighter="Contrôle si un joueur peut utiliser un briquet." + flag-description-custom-lightning="Contrôle si un éclair peut causer des dégâts." + flag-description-custom-monster-damage="Contrôle si un monstre peut faire des dégâts." + flag-description-custom-mushroom-growth="Contrôle si les champignons peuvent grandir." + flag-description-custom-mycelium-spread="Contrôle si le mycelium peut s'étendre." + flag-description-custom-pistons="Contrôle si un piston peut être utilisé." + flag-description-custom-portal-use="Contrôle si un portail peut être utilisé." + flag-description-custom-pvp="Contrôle si le combat Joueur contre Joueur est autorisé." + flag-description-custom-ride="Contrôle si les véhicules (incluant les animaux) peuvent être montés." + flag-description-custom-sleep="Contrôle si les joueurs peuvent dormir dans les lits." + flag-description-custom-snow-fall="Contrôle si la neige peut tomber." + flag-description-custom-snow-melt="Contrôle si la neige peut fondre." + flag-description-custom-snowman-trail="Contrôle si un bonhomme de neige peut laisser de la neige derrière lui." + flag-description-custom-soil-dry="Contrôle si la terre peut sécher." + flag-description-custom-spawn-ambient="Contrôle si les environnementaux, comme les chauves-souris, peuvent apparaître." + flag-description-custom-spawn-animal="Contrôle si les animaux, comme les vaches ou cochons, peuvent apparaître." + flag-description-custom-spawn-aquatic="Contrôle si les aquatiques, comme les poulpes et gardiens, peuvent apparaître." + flag-description-custom-spawn-monster="Contrôle si les monstres, comme les creepers ou les squelettes, peuvent apparaître." + flag-description-custom-teleport-from="Contrôle si les joueurs peuvent se téléporter depuis la protection." + flag-description-custom-teleport-to="Contrôle si les joueur peuvent se téléporter vers la protection." + flag-description-custom-use="Contrôle si les joueurs peuvent utiliser des blocs sans inventaire dans la protection." + flag-description-custom-vehicle-destroy="Contrôle si un vehicule peut être détruit." + flag-description-custom-vehicle-place="Contrôle si un véhicule (bateau, minecart) peut être placé." + flag-description-custom-vine-growth="Contrôle si les vignes (et les algues) peuvent grandir." + flag-description-custom-water-flow="Contrôle si l'eau peut couler." + flag-description-custom-wither-damage="Contrôle si un Withers peut faire des dégâts." + flag-description-enter-claim="Contrôle si une entité peut rentrer dans une protection.\n&dExemple&f : Pour prévenir les joueurs de rentrer dans une protection, entre\n&a/cf enter-claim player false\n&bNote&f : Si tu veux utiliser ça sur un groupe, utilises la commande /cfg." + flag-description-entity-chunk-spawn="Contrôle si une entité sauvegardée peut apparaître pendant le chargement d'un chunk.\n&dExemple&f : Pour prévenir les chevaux d'apparaître pendant le chargement du chunk, entre\n&a/cf entity-chunk-spawn minecraft:horse false\n&bNote&f : Cela va supprimer l'ENSEMBLE des entités sauvegardées quand le chunk va charger. Si le chunk est déjà chargé, l'effet aura lieu après le rechargement. À utiliser avec une extrême prudence." + flag-description-entity-damage="Contrôle si une entité peut prendre des dégâts.\n&dExemple&f : Pour prévenir les animaux de prendre des dégâts, entre\n&a/cf entity-damage minecraft:animal false." + flag-description-entity-riding="Contrôle si une entité peut être chevauchée.\n&dExemple&f : Pour prévenir les chevaux d'être montés, entre\n&a/cf entity-riding minecraft:horse false." + flag-description-entity-spawn="Contrôle si une entité peut apparaître dans le monde.\n&dExemple&f : Pour prévenir les cochons d'apparaîtres, entre\n&a/cf entity-spawn minecraft:pig false\n&bNote&f : Cela n'inclut pas les objets d'entités. Regarde les flags item-spawn" + flag-description-entity-teleport-from="Contrôle si une entité peut se téléporter depuis une protection.\n&dExemple&f : Pour prévenir les joueurs de se téléporter depuis la protection, entre\n&a/cf entity-teleport-from player false\n&bNote&f : Si tu veux utiliser ça pour les groupes, utilises la commande /cfg." + flag-description-entity-teleport-to="Contrôle si une entité peut se téléporter dans une protection.\n&dExemple&f : Pour prévenir les joueur de se téléporter dans une protection, entre\n&a/cf entity-teleport-to player false\n&bNote&f : Si tu veux utiliser ça pour les groupes, utilises la commande /cfg." + flag-description-exit-claim="Contrôle si une entité peut sortir d'une protection.\n&dExample&f : Pour prévenir les joueurs de sortir d'une protection, entre\n&a/cf exit-claim player false\n&bNote&f : Si tu veux utiliser ça pour les groupes, utilises la commande /cfg." + flag-description-explosion-block="Contrôle si une explosion peut endommager les blocs dans la protection.\n&dExemple&f : Pour prévenir une explosion d'affecter n'importe quel bloc, entre\n&a/cf explosion-block any false" + flag-description-explosion-entity="Contrôle si une explosion peut blesser une entité dans une protection.\n&dExample&f : Pour prévenir une explosion d'affecter n'importe quel entité, entre\n&a/cf explosion-entity any false" + flag-description-interact-block-primary="Contrôle si un joueur peut faire un clique-gauche(attaque) un bloc.\n&dExemple&f : Pour prévenir un joueur de faire un clique-gauche sur un coffre, entre\n&a/cf interact-block-primary minecraft:chest false" + flag-description-interact-block-secondary="Contrôle si un joueur peut faire un clique-droit sur un bloc.\n&dExemple&f : Pour prévenir un joueur de faire un clique-droit(ouvrir) sur un coffre, entre\n&a/cf interact-block-secondary minecraft:chest false" + flag-description-interact-entity-primary="Contrôle si un joueur peut faire un clique-gauche(attaque) sur une entité.\n&dExemple&f : Pour prévenir un joueur de faire un clique-gauche sur une vache, entre\n&a/cf interact-entity-primary minecraft:cow false" + flag-description-interact-entity-secondary="Contrôle si un joueur peut faire un clique-droit sur une entité.\n&dExemple&f : Pour prévenir un joueur d'intéragir avec un villageois, entre\n&a/cf interact-entity-secondary minecraft:villager false" + flag-description-interact-inventory="Contrôle si un joueur peut faire un clique-droit(ouvrir) un bloc avec un inventaire comme un coffre.\n&dExemple&f : Pour prévenir un joueur d'ouvrir un coffre, entre\n&a/cf interact-inventory minecraft:chest false" + flag-description-interact-inventory-click="Contrôle si un joueur peut cliquer sur un emplacement d'inventaire.\n&dExemple&f : Pour prévenir un joueur de cliquer sur un emplacement d'inventaire qui contient un diamant, entre\n&a/cf interact-inventory-click minecraft:diamond false" + flag-description-interact-item-primary="Contrôle si un joueur peut clique-gauche(attaque) avec un objet.\n&dExemple&f : Pour prévenir un joueur de clique-gauche avec un épée en diamant, entre\n&a/cf interact-item-primary minecraft:diamond_sword false" + flag-description-interact-item-secondary="Contrôle si un joueur peut clique-droit avec un objet.\n&dExample&f : Pour prévenir un joueur de faire un clique droit avec un briquet, entre\n&a/cf interact-item-secondary minecraft:flint_and_steel false" + flag-description-item-drop="Contrôle si un objet peut être lâché dans une protection.\n&dExemple&f : Pour prévenir le diamant d'être lâché par les joueurs dans la protection, entre\n&a/cf item-drop minecraft:flint_and_steel false context[source=player]" + flag-description-item-pickup="Contrôle si un item peut être ramassé dans la protection.\n&dExemple&f : Pour prévenir un diamant d'être ramassé par les joueurs, entre\n&a/cf item-pickup minecraft:diamond false" + flag-description-item-spawn="Contrôle si un objet peut apparaître dans une protection.\n&dExemple&f : Pour prévenir les plumes d'apparaître dans la protection, entre\n&a/cf item-spawn minecraft:feather false" + flag-description-item-use="Contrôle si un objet peut être utilisé.\n&dExemple&f : Pour prévenir les pommes d'être mangées dans la protection, entre\n&a/cf item-use minecraft:apple false" + flag-description-leaf-decay="Contrôle si les feuilles peuvent dépérir dans la protection.\n&dExemple&f : Pour prévenir les feuilles de dépérir dans la protection, entre\n&a/cf leaf-decay any false" + flag-description-liquid-flow="Contrôle si un liquide, comme la lave ou l'eau, est autorisé à couler dans la protection.\n&dExemple&f : Pour prévenir n'importe quel type de liquide de couler dans la protection, entre\n&a/cf liquid-flow any false" + flag-description-portal-use="Contrôle si un portail peut être utilisé.\n&dExemple&f : Pour prévenir seulement les joueurs d'utiliser un portail sans affecter les non joueurs, entre\n&a/cf portal-use any false context[source=player]" + flag-description-projectile-impact-block="Contrôle si un projectile peut avoir un impact(collision) avec un bloc.\n&dExemple&f : Pour prévenir les pokéballs de pixelmon d'impacter un bloc, entre\n&a/cf projectile-impact-block any false[source=pixelmon:occupiedpokeball]\n&bNote&f : Cela concerne les choses comme les potions, flèches, lançable, pokéball pixelmon, etc." + flag-description-projectile-impact-entity="Contrôle si un projectile peut avoir un impact(collision) avec une entité.\n&dExample&f : Pour prévenir les joueurs de faire un tir de flèche pouvant impacter une entité, entre\n&a/cf projectile-impact-entity minecraft:arrow false[source=player]\n&bNote&f : Cela concerne les choses comme les potions, flèches, lançable, pokéball pixelmon, etc." + flag-invalid-context="&cContexte entré '&f{context}&c' invalide pour le flag de base &f{flag}&c." + flag-invalid-meta="&cCible meta entrée '&f{value}&c' invalide pour le flag de base &f{flag}&c." + flag-invalid-target="&cCible '&f{target}&c' entré invalide pour le flag de base &f{flag}&c." + flag-not-found="&cFlag {flag}&c introuvable." + flag-not-set="{flag}&f est actuellement non définis.\nLa valeur par défaut {value}&f de la protection sera active jusqu'à définition." + flag-overridden="&cÉchec de la définition du flag de protection. Le flag &f{flag}&c a été outrepassé par un admin." + flag-override-not-supported="&cLe type de protection {type}&c ne supporte pas les flags outrepassés." + flag-reset-success="&aFlags remis par défaut dans la protection avec succès." + flag-reset-warning="&6Es-tu sûr de vouloir remettre par défaut les paramètres de la protection ?" + flag-set-permission-target="&aDéfinis {type}&a permission &b{permission}&a avec contexte &7{contexts}&a à {value}&a sur &6{target}&a." + flag-ui-click-allow="Clique ici pour autoriser ce flag." + flag-ui-click-deny="Clique ici pour interdire ce flag." + flag-ui-click-remove="Clique ici pour supprimer ce flag." + flag-ui-click-toggle="Clique ici pour basculer la valeur de {flag}&f." + flag-ui-info-claim="La protection est vérifiée avant les valeurs par défaut. Autorise les propriétaires de protection de spécifier le paramètre de flag dans leurs protections seulement." + flag-ui-info-default="Default est le dernier à être vérifié. Protection et Outrepassant prenent la priorité sur cela." + flag-ui-info-inherit="Héritage est un flag forcé par la protection parente et ne peut être changé." + flag-ui-info-override="Outrepassant est la plus haute priorité et est vérifié avant les valeurs Default et Protection. Permet aux admins d'outrepasser l'ensemble des protections Basiques et Admins." + flag-ui-inherit-parent="Ce flag hérite depuis la protection parente {name}&f et ne peut &npas&f être changé." + flag-ui-override-no-permission="Ce flag a été outrepassé par un administrateur et ne peut &n&cPAS&f être changé." + flag-ui-override-permission="{flag}&f est actuellement &coutrepassé&f par un administrateur.\nClique ici pour supprimer ce flag." + flag-ui-return-flags="Retourne au flag" + label-accessors=Accédant + label-area=Zone + label-blocks=Blocs + label-builders=Constructeurs + label-buy=Achat + label-cancel=Annuler + label-children=Enfant + label-confirm=Confirme + label-containers=Conteneur + label-context=Contexte + label-created=Créé + label-displaying=Affiche + label-expired=Expiré + label-farewell="Message de sortie" + label-flag=Flag + label-greeting="Message d'accueil" + label-group=Groupe + label-inherit=Héritage + label-location=Location + label-managers=Manageurs + label-name=Nom + label-no=Non + label-output=Sortie + label-owner=Propriétaire + label-permission=Permission + label-player=Joueur + label-price=Prix + label-raid=Raid + label-resizable=Redimensionnable + label-result=Résultat + label-schematic=Patron + label-source=Source + label-spawn="Point d'apparition" + label-target=Cible + label-trust=Confiance + label-type=Type + label-unknown=Inconnu + label-user=Utilisateur + label-world=Monde + label-yes=Oui + mode-admin="&aMode protection Administratif activé. Chaque protection créée sera gratuite et éditable par les autres administrateur." + mode-basic="&aMode de création protection basique activé." + mode-nature="&aPrêt pour restaurer la protection ! Clique-droit sur un bloc pour restaurer, et utilises &f/modebasic&c pour arrêter." + mode-subdivision="&aMode Sous-divions. Utilises la pelle pour créer une sous-division dans ta protection existante. Utilises &f/modebasic&a pour sortir." + mode-town="&aMode création de Village activé." + option-description-abandon-delay="&aLe nombre de jours avant qu'une nouvelle protection créée puisse être abandonnée." + option-description-abandon-return-ratio="&aLa portion de bloc de protection basique rendu au joueur quand une protection est abandonnée." + option-description-blocks-accrued-per-hour="&aBlocs gagnés par heure.\n&dNote&f: Regarde /playerinfo pour plus d'informations." + option-description-chest-expiration="&aNombre de jour d'inactivité avant qu'une protection de coffre automatique expire.\n&dNote&f: Lors de l'expiration, une protection peut soit être restaurée à son état d'origine ou supprimée. Cela dépend de la configuration du serveur. Contacte un administrateur pour plus d'informations." + option-description-create-limit="&aNombre maximum de protection par joueur.\n&dNote&f: Mettre une valeur en dessous de 0 donnera illimité." + option-description-create-mode="&aLe mode de création de la protection (Zone = 2D, Volume = 3D).\n&dNote&f: &bZone&a affecte seulement les axes x et y.\n&bVolume&a affecte les axes x, y, et z." + option-description-economy-block-cost="&aLe montant économique chargé par bloc de protection.\n&dNote&f: La formule de calcul du prix est montant * nombre total de blocs de protection." + option-description-economy-block-sell-return="&aLe ratio de retour pour vendre des blocs de protection.\n&dNote&f: La formule de calcul est ratio de retour * nombre total de blocs de protection." + option-description-expiration="&aNombre de jour d'inactivité avant que la protection expire.\n&dNote&f: Lors de l'expiration, une protection peut soit être restaurée à son état d'origine ou supprimée. Cela dépend de la configuration du serveur. Contacte un administrateur pour plus d'informations." + option-description-initial-blocks="&aLe nombre de blocs de protection qu'a initialement un joueur, par défaut." + option-description-max-accrued-blocks="&aLa limite de bloc accrues (dans le temps).\n&dNote&f: Cela ne limite pas les blocs achetés ou donnés par un admin." + option-description-max-level="&aLe niveau maximum, sur l'axe y, une protection peut être créée." + option-description-max-size-x="&aLa taille maximum de blocs l'axe x peut être." + option-description-max-size-y="&aLa taille maximum de blocs l'axe y peut être." + option-description-max-size-z="&aLa taille maximum de blocs l'axe z peut être." + option-description-min-level="&aLe niveau minimum, sur l'axe y, une protection peut être créée." + option-description-min-size-x="&aLa taille minimum de blocs l'axe x peut être." + option-description-min-size-y="&aLa taille minimum de blocs l'axe y peut être." + option-description-min-size-z="&aLa taille minimum de blocs l'axe z peut être." + option-description-player-command="&aUtilisé pour exécuter une commande avec un contexte spécifique." + option-description-player-deny-flight="&aUtilisé pour déterminer si un joueur est incapable de fly dans une protection.\n&dNote&f: Cela ne donne pas l'abilité de fly au joueur, ça supprime juste l'abilité si elle a été donnée. Cela donne la meilleurs compatibilité avec les plugins." + option-description-player-deny-godmode="&aUtilisé pour déterminer si un joueur est incapable de godmode dans une protection.\n&dNote&f: Cela ne donne pas l'abilité de godmode au joueur, ça supprime juste l'abilité si elle a été donnée. Cela donne la meilleurs compatibilité avec les plugins." + option-description-player-deny-hunger="&aUtilisé pour refuser la famine dans une protection.\n&dNote&f: Cela ne donne pas l'abilité de gagner de la famine au joueur, ça supprime l'abilité de cause de la famine si défini. Cela donne la meilleurs compatibilité avec les plugins." + option-description-player-gamemode="&aUtilisé pour déterminer le gamemode d'un joueur dans un protection." + option-description-player-health-regen="&aUtilisé pour définir le nombre de vies régénérée pour un joueur dans la protection.\n&dNote&f: Si le joueur a la vie au maximum, cela n'aura pas d'effet. \n&dNote&f: Une valeur de&6-1&f désactive cette option." + option-description-player-keep-inventory="&aUtilisé pour déterminer si un joueur à le droit de garder son inventaire après la mort dans une protection." + option-description-player-keep-level="&aUtilisé pour déterminer si un joueur à le droit de garder son niveau après la mort dans une protection." + option-description-player-walk-speed="&aUtilisé pour définir la vitesse de marche dans une protection.\n&dNote&f: Une valeur de &6-1&f désactive cette option." + option-description-player-weather="&aUtilisé pour définir la météo d'un joueur dans une protection." + option-description-radius-inspect="&aLe rayon de recherche pour les protections à proximité lors de l'inspection." + option-description-radius-list="&aLe rayon en blocs utilisés pour lister les protections à proximité." + option-description-tax-expiration="&aNombre de jour après ne pas avoir payé les taxes avant que la protection soi mise en demeure.\n&dNote&f: Une mise en demeure signifie que tu n'auras plus accès à la construction ou l'intération avec la protection jusqu'au paiement des taxes." + option-description-tax-expiration-days-keep="&aNombre de jour pour garder une protection basique mise en demeure et avant expiration.\n&dNote&f: Lors de l'expiration, une protection peut soit être restaurée à son état d'origine ou supprimée. Cela dépend de la configuration du serveur. Contacte un administrateur pour plus d'informations." + option-description-tax-rate="&aLe taux de taxe de la protection.\n&dNote&f: Le taux de taxe est calculé par le nombre de blocs de protection dans les protections basiques." + option-invalid-context="&cContexte '&f{context}&c' invalide entré pour l'option &f{option}&c." + option-invalid-target="&cCible '&f{target}&c' invalide entrée pour l'option &f{option}&c." + option-invalid-value="&cValeur '&6{value}&c' invalide entrée pour l'option &f{option}&c.\n&dNote&f: Cette option accepte uniquement les valeurs &f{type}&c." + option-not-found="&cL'option {option}&c n'a pas été trouvée." + option-not-set="{option}&f est actuellement non définit.\n&dNote&f: La valeur par défaut {value}&f de l'option sera active jusqu'à définition." + option-override-not-supported="&cProtection de type {type}&c ne supporte pas les options outrepassantes." + option-player-deny-flight="&cTu n'as pas accès au fly dans cette protection et a été téléporté dans une zone sécurisée au sol." + option-reset-success="&aOption de la protection remises par défaut avec succès." + option-set-target="&aDéfinis {type}&a de l'option &b{option}&a à {value}&a avec le contexte &7{contexts}&a sur la cible &6{target}&a." + option-ui-click-toggle="Clique ici pour changer la valeur de {option}&f." + option-ui-inherit-parent="Cette option est héritée depuis la protection parente {name}&f et ne peut &nPAS&f être changée." + option-ui-overridden="&cÉchec de la définition de l'option. L'option &f{option}&c a été outrepassée par un admin." + option-ui-override-no-permission="Cette option a été outrepassée par un administration et ne peut &n&cPAS&f être changée." + owner-admin="un administrateur" + permission-access="&cTu n'as pas la permission de &6{player}&c pour accéder à ça." + permission-assign-without-having="&cTu n'as pas l'autorisation d'assigner une permission que tu ne possèdes pas." + permission-ban-block="&cLe bloc {id}&c a été &l&nBANNIT&c et ne peut être utilisé." + permission-ban-entity="&cL'entité {id}&c a été &l&nBANNIT&c et ne peut être utilisé." + permission-ban-item="&cL'objet {id}&c a été &l&nBANNIT&c et ne peut être utilisé." + permission-build="&cTu n'as pas la permission de &6{player}&c pour construire." + permission-build-near-claim="&cTu n'as pas la permission de &6{player}&c de construire à proximité de protection." + permission-claim-create="&cTu n'as pas la permission de protéger une zone." + permission-claim-delete="&cTu n'as pas la permission de supprimer les protections {type}&c." + permission-claim-enter="&cTu n'as pas la permission de rentrer dans cette protection." + permission-claim-exit="&cTu n'as pas la permission de sortir de cette protection." + permission-claim-ignore="&cTu n'as pas la permission pour ignorer les protections {type}&c." + permission-claim-list="&cTu n'as pas la permission pour récupérer les informations concernant les protections d'autres joueurs." + permission-claim-manage="&cTu n'as pas la permission pour gérer les protections {type}&c." + permission-claim-reset-flags="&cTu n'as pas la permission de remettre par défaut les flags dabs les protections {type}&c." + permission-claim-reset-flags-self="&cTu n'as pas la permission pour remettre par défaut les flags dans tes protections." + permission-claim-resize="&cTu n'as pas la permission pour redimensionner cette protection." + permission-claim-sale="&cTu n'as pas la permission pour vendre cette protection." + permission-claim-transfer-admin="&cTu n'as pas la permission de transférer les protections admin." + permission-clear="&cNettoyage des permissions de cette protection. Pour le définir pour l'ENSEMBLE des protections, sors de la zone des protections." + permission-clear-all="&cSeulement le propriétaire de la protection peut nettoyer l'ensemble des permissions." + permission-command-trust="&cTu n'as pas la permission pour utiliser ce type de confiance." + permission-cuboid="&cTu n'as pas la permission pour créer/redimensionner les protections basiques en mode 3D." + permission-edit-claim="&cTu n'as pas la permission pour éditer cette protection." + permission-fire-spread="&cTu n'as pas la permission pour propager le feu dans cette protection." + permission-flag-defaults="&cTu n'as pas la permission pour gérer les flags par défaut." + permission-flag-overrides="&cTu n'as pas la permission pour gérer les flags outrepassant." + permission-flag-use="&cTu n'as pas la permission pour utiliser ce flag." + permission-flow-liquid="&cTu n'as pas la permission pour faire couler le liquide dans cette protection." + permission-global-option="&cTu n'as pas la permission pour gérer les options globals." + permission-grant="&cTu ne peut pas te donner une permission que tu ne possèdes pas toi-même." + permission-group-option="&cTu n'as pas la permission pour assigner une option à un groupe." + permission-interact-block="&cTu n'as pas la permission de &6{player}&c pour intéragir avec le bloc &d{block}&c." + permission-interact-entity="&cTu n'as pas la permission de &6{player}&c pour intéragir avec l'entité &d{entity}&c." + permission-interact-item="&cTu n'as pas la permission de &6{player}& pour intéragir avec l'objet &d{item}&c." + permission-interact-item-block="&cTu n'as pas la permission d'utiliser l'objet &d{item}&c sur &b{block}&c." + permission-interact-item-entity="&cTu n'as pas la permission d'utiliser l'objet &d{item}&c sur &b{entity}&c." + permission-inventory-open="&cTu n'as pas la permission de &6{player}&c d'ouvrir &d{block}&c." + permission-item-drop="&cTu n'as pas la permission de &6{player}&c pour jeter l'objet &d{item}&c dans cette protection." + permission-item-use="&cTu ne peut pas utiliser l'objet &d{item}&c dans cette protection." + permission-option-defaults="&cTu n'as pas la permission pour gérer les options par défaut." + permission-option-overrides="&cTu n'as pas la permission pour gérer les options outrepassant." + permission-option-use="&cTu n'as pas la permission d'utiliser cette option." + permission-override-deny="&cL'action que tu essayes d'effectuer a été refusée par un flag outrepassant administrateur." + permission-player-admin-flags="&cTu n'as pas la permission de changer un flag sur un joueur admin." + permission-player-option="&cTu n'as pas la permission pour assigner une option sur un joueur." + permission-player-view-others="&cTu n'as pas la permission pour voir les autres joueurs." + permission-portal-enter="&cTu ne peut pas utiliser le portail car tu n'as pas la permission de &6{player}&c d'entrer dans la protection de destination." + permission-portal-exit="&cTu ne peut pas utiliser le portail car tu n'as pas la permission de &6{player}&c pour sortir de la protection de destination." + permission-protected-portal="&cTu n'as pas la permission d'utiliser les portails dans les protections appartenant à &6{player}&c." + permission-trust="&cTu n'as pas la permission de &6{player}&c pour gérer les permissions ici." + permission-visual-claims-nearby="&cTu n'as pas la permission pour voir les protections à proximité." + player-accrued-blocks-exceeded="&cLe joueur &6{player}&c a un total de &6{total}&c et vas dépasser le maximum autorisé de blocs de protection gagnés s'il est donné un nombre additionnel de &6{amount}&c bloc.\nDescends le nombre ou demande un admin de donner à l'utilisateur un outrepassement." + player-remaining-blocks-2d="&aTu peut protéger jusqu'à &6{block-amount}&a blocs supplémentaires." + player-remaining-blocks-3d="&aTu peut protéger jusqu'à &6{chunk-amount}&a chunks supplémentaire. &f({block-amount})" + playerinfo-ui-abandon-return-ratio="&eAbandonné, Ratio de retour&f : &a{ratio}" + playerinfo-ui-block-accrued="&eBlocs gagnés&f : &a{amount}&7(&d{block_amount}&f par heure&7)" + playerinfo-ui-block-bonus="&eBlocs bonus&f : &a{amount}" + playerinfo-ui-block-initial="&eBloc Initial&f : &a{amount}" + playerinfo-ui-block-max-accrued="&eMaximum de blocs gagnés&f : &a{amount}" + playerinfo-ui-block-remaining="&eBlocs restants&f : &a{amount}" + playerinfo-ui-block-total="&eTotal de Blocs&f : &a{amount}" + playerinfo-ui-chunk-total="&eTotal Chunks Protectable&f : %a{amount}" + playerinfo-ui-claim-level="&eMin/Niveau de protection max&f : &a{level}" + playerinfo-ui-claim-size-limit="&eLimite de taille de protection&f : &a{limit}" + playerinfo-ui-claim-total="&eProtections Total&f : &a{amount}" + playerinfo-ui-economy-block-available-purchase="&eBlocs restants pour l'achat&f : &a{amount}" + playerinfo-ui-economy-block-cost="&ePrix de blocs de protection&f : &a{amount} par bloc" + playerinfo-ui-economy-block-sell-return="&ePrix de retour de blocs de proteciton vendus&f : &a{amount} par bloc" + playerinfo-ui-last-active="&eDernière Activité&f : {date}" + playerinfo-ui-tax-current-rate="&eTaux de taxe actuel de la protection&f : &a{rate}" + playerinfo-ui-tax-global-claim-rate="&eTaux de taxe de la protection Global&f : &a{rate}" + playerinfo-ui-tax-global-town-rate="&eTaux de taxe de Village Global&f : &a{rate}" + playerinfo-ui-tax-total="&eTaxe Total&f : &a{amount}" + playerinfo-ui-title="&bInfo joueur" + playerinfo-ui-uuid="&eUUID&f : &7{id}" + playerinfo-ui-world="&eMonde&f : &7{name}" + plugin-command-not-found="&cImpossible de localiser la commande '&a{command}&c' pour le plug-in &b{id}&a." + plugin-event-cancel="&cUn plug-in a annulé cette action." + plugin-not-found="&cImpossible de localiser le plug-in avec l'id &b{id}&c." + plugin-reload="&aGriefDefender a été rechargé." + pvp-claim-not-allowed="&aTu n'as pas le droit de PvP dans cette protection." + pvp-source-not-allowed="&aTu n'as pas le droit de PvP." + pvp-target-not-allowed="&aTu ne peut pas attaquer les joueurs qui ne participent pas au PvP." + registry-block-not-found="&cLe bloc {id} ne peut pas être trouvé dans le registre." + registry-entity-not-found="&cL'entité {id} ne peut pas être trouvé dans le registre." + registry-item-not-found="&cL'objet {id} ne peut pas être trouvé dans le registre." + resize-overlap="&cImpossible de redimensionner ici car cela chauvecherait une protection à proximité." + resize-overlap-subdivision="&cTu ne peut pas créer une sous-division ici car cela chevaucherait une autre sous-division. Considère &f/abandon&c pour la supprimer ou utilise la pelle sur un coin pour le redimensionner." + resize-same-location="&cTu dois sélectionner un bloc à un endroit différent pour redimensionner une protection." + resize-start="&aRedimensionnement de la protection. Utilise la pelle à nouveau au nouvel endroit pour ce coin." + resize-success-2d="&aProtection redimensionnée. Tu as encore &6{amount} &ablocs restants." + resize-success-3d="&aProtection redimensionnée. Tu as encore &6{amount} &achunks restants. &f({block-amount})" + result-type-change-deny="&cTu ne peut pas changer une protection en {type}." + result-type-change-not-admin="&cTu n'as pas la permission d'un administrateur pour changer le type en {type}&c." + result-type-child-same="Protection {type}&c ne peuvent pas avoir directement des protections enfant de type {type}&c." + result-type-create-deny="{type}&c ne peut pas être créé dans {target_type}." + result-type-no-children="{type}&c ne peut pas contenir de protection enfant." + result-type-only-subdivision="{type}&c peut seulement contenir des sous-division." + result-type-requires-owner="&cImpossible de convertir la protection {type} en {target_type}. Le propriétaire est requis." + schematic-abandon-all-restore-warning="&6Es-tu sûr de vouloir &nabandonner&6 &cTOUTES&6 tes protections ? &cL'ENSEMBLE DES DONNÉES SERONT PERDUES&f !&6 Tes protections seront restorées à leur état d'origine lors de la confirmation." + schematic-abandon-restore-warning="&6Es-tu sûr de vouloir &nabandonner&6 cette protection ? &cL'ENSEMBLE DES DONNÉES SERONT PERDUES&f !&6 Cette protection sera restorée à son état d'origine lors de la confirmation." + schematic-create="&aCréation d'une sauvegarde du patron..." + schematic-create-complete="&aSauvegarde du patron complète." + schematic-create-fail="&cLe patron n'a pas pu être créé." + schematic-deleted="&aLe patron {name} a été supprimé." + schematic-none="&aIl n'y a pas de patron de sauvegardé pour cette protection." + schematic-restore-click="&aCLique ici pour restaurer le patron de la protection.\nNom: {name}\nCréé: {date}" + schematic-restore-confirmation="&6Es-tu sûr de vouloir restaurer ? Clique confirme va restaurer l'&ENSEMBLE&6 des données de la protection avec le patron. Utilisation avec prudence !" + schematic-restore-confirmed="&aTu as restauré la protection depuis le patron sauvegardé &b{name}&a avec succès." + spawn-not-set="&cPas de point d'apparition de la protection définis." + spawn-set-success="&aDéfinition du point d'apparition à &b{location}&a avec succès." + spawn-teleport="&aTéléportation au point d'apparition de la protection à &b{location}&a." + tax-claim-expired="&cCette protection a été mise en demeure à cause de taxes impayées. Le montant actuel du est de '&a{amount}&c'.\nIl reste '&a{days}&c' jours pour effectuer le dépôt de paiement à la banque de protection pour lever la mise en demeure.\nNe pas payer cette dette aura pour conséquence la suppression de la protection.\nNote: Pour déposer des fonds dans la banque de protection, utilises &f/claimbank&c deposit <nombre>." + tax-claim-paid-balance="&aLa dette de taxe de '&6{amount}&a' a été payée. La mise en demeure a été levée et la protection est disponible pour usage." + tax-claim-paid-partial="&aLa dette de taxe de '&6{amount}&a' a été partiellement payée. Pour lever la mise en demeure de la protection, le reste de la taxe due de '&6{balance}&a' doit être payé." + tax-info="&aTon prélèvement de taxe d'un montant de &6{amount}&a va être prélevé depuis ton compte le &b{date}&a." + tax-past-due="&cTu as actuellement un défaut de paiement passé de taxe de &a{balance}&c qui nécessite d'être payé pour le &b{date}&c. L'échec de paiement de cette taxe entrainera la perte de la propriété." + teleport-confirm="&aEs-tu sûr de vouloir te téléporter à {pos}? Clique confirme pour procéder." + teleport-delay-notice="&aTu seras téléporté dans {delay} secondes. Clique sur annuler pour arrêter." + teleport-move-cancel="&cTéléportation annulée ! Tu ne peux pas bouger lors d'une tentative de téléportation." + teleport-no-safe-location="&cPas de zone sécurisée trouvée dans la protection pour se téléporter !\n&aClique confirm pour téléporter malgré tout ou &autilise la commande '&f/claiminfo&a' pour définir une un point d'apparition sécurisé à la place." + teleport-success="&aTu as été téléporté à {name}&a." + title-accessor=ACCÉDANT + title-all=TOUS + title-builder=CONSTRUTEUR + title-claim=PROTECTION + title-container=CONTENEUR + title-default=DEFAUT + title-inherit=HÉRITAGE + title-manager=GÉRANT + title-override=OUTREPASSANT + title-own=POSSÈDE + tool-not-equipped="&cTu n'as pas {tool}&c équipé." + town-chat-disabled="&aChat de Village désactivé." + town-chat-enabled="&aChat de Village activé." + town-create-not-enough-funds="&cTy n'as pas suffisamment de fonds pour créer un village pour &a{amount}&c. Tu as actuellement un solde de &a{balance}&c et a besoin de &a{amount-needed}&c supplémentaire pour la création." + town-name="&aDéfinis le nom du village à {name}&a." + town-not-found="&cVillage non trouvé." + town-not-in="&cTu n'es pas dans un Village." + town-owner="&cCela appartient au village." + town-tag="&aDéfinis le blason du village à {tag}." + town-tag-clear="&aLe blason du village a été supprimé." + town-tax-no-claims="&cTu dois avoir une propriété dans ce village pour être taxé." + trust-already-has="&c{target} a déjà la permission {type}&c." + trust-click-show-list="Clique ici pour afficher la liste de l'ensemble des players et groupes ayant la confiance dans cette protection." + trust-grant="&aDonne à &6{target}&a la permission de {type}&a dans la protection actuelle." + trust-individual-all-claims="&aDonne &6{player}'s&a total confiance dans l'ENSEMBLE de tes protections. Pour supprimer les permissions dans l'ENSEMBLE de tes protections, utilises &f/untrustall&a." + trust-invalid="&cType de confiance invalide entré.\nLes types autorisés sont : accessor, builder, container, et manager." + trust-list-header="Permission explicite ici :" + trust-no-claims="&cTu n'as pas de protection pour donner Confiance." + trust-plugin-cancel="&cImpossible d'avoir Confiance en {target}&c. Un plug-in l'a refusé." + trust-self="&cTu ne peut pas te faire Confiance à toi-même." + tutorial-claim-basic="&eClique pour l'aide sur la protection: &ahttp://bit.ly/mcgpuser" + ui-click-confirm="Clique pour confirmer." + ui-click-filter-type="Clique ici pour filtrer par {type}&f." + untrust-individual-all-claims="&aRévoque &6{target}&a accès à l'ENSEMBLE de tes protections. Pour définir la permission pour une seule protection, va dedans et utilises &f/untrust&a." + untrust-individual-single-claim="&aRévoque &6{target}&a accès à cette protection. Pour définir la permission pour l'ENSEMBLE de tes protections, utilises &f/untrustall&a." + untrust-no-claims="&cTu n'as pas de protection où enlever la confiance." + untrust-owner="&6{owner}&a est le propriétaire et ne pas pas perdre la confiance." + untrust-self="&cTu ne peut pas te retirer la confiance toi-même." + } +} diff --git a/sponge/src/main/resources/assets/lang/ru_RU.conf b/sponge/src/main/resources/assets/lang/ru_RU.conf new file mode 100644 index 0000000..0583ea8 --- /dev/null +++ b/sponge/src/main/resources/assets/lang/ru_RU.conf @@ -0,0 +1,634 @@ +GriefDefender { + descriptions { + abandon-all="Удалить ВСЕ ваши регионы." + abandon-claim="Удалить регион." + abandon-top="Удалить регион верхнего уровня." + buy-blocks="Увеличить за плату серверной валютой количество доступных блоков региона.\nВнимание: требует плагин для экономики." + callback="Запустить функцию, зарегистрированную как часть текстового объекта. Предназначена для внутреннего использования." + claim-bank="Используется для снятия или начисления денег для использования в регионе." + claim-clear="Позволяет очистить сущности в одном или нескольких регионах." + claim-debug="Переключить режим отладки регионов." + claim-farewell="Установить сообщение при выходе из региона." + claim-greeting="Установить сообщение при входе в регион." + claim-ignore="Переключить режим игнорирования разрешений в регионах." + claim-info="Вывести всю доступную информацию о регионе, в котором вы находитесь." + claim-inherit="Переключить наследование текущим регионом разрешений от родителя(ей)." + claim-list="Вывести все регионы в заданной области." + claim-name="Установить имя региона." + claim-restore="Восстановить регион в его начальное состояние. Использовать с осторожностью." + claim-setspawn="Установить точку возрождения игроков в этом регионе." + claim-spawn="Телепортироваться в точку возрождения игроков в этом регионе, если она задана." + claim-transfer="Передать обычный или администраторский регион другому игроку." + claim-worldedit="Создать регион, используя выделенную при помощи WorldEdit область." + cuboid="Переключить режим создания регионов - кубоид/чанк." + debug="Собирать все действия GD для отладки." + delete-all="Удалить все регионы другого игрока." + delete-all-admin="Удалить все администраторские регионы." + delete-claim="Удалить регион, в котором вы находитесь, даже если он вам не принадлежит." + delete-top="Удалить регион, в котором вы находитесь, вместе с его суб-регионами, даже если он вам не принадлежит." + flag-claim="Вывести/Настроить флаги в регионе, в котором вы находитесь." + flag-group="Вывести/Настроить разрешения флагов для группы в регионе, в котором вы находитесь." + flag-player="Вывести/Настроить разрешения флагов для игрока в регионе, в котором вы находитесь." + flag-reset="Установить значения по умолчанию для всех флагов в регионе." + mode-admin="Переключить лопату в режим создания администраторских регионов." + mode-basic="Переключить лопату в режим создания обычных регионов." + mode-nature="Переключить лопату в режим восстановления." + mode-subdivision="Переключить лопату в режим создания суб-регионов." + mode-town="Переключить лопату в режим создания городских регионов." + option-claim="Вывести/Настроить опции в регионе, в котором вы находитесь." + permission-group="Установить разрешение для группы в контексте региона." + permission-player="Установить разрешение для игрока в контексте региона." + player-adjust-bonus-blocks="Обновить общее количество бонусных блоков региона для игрокв." + player-info="Показать информацию об игроке." + player-set-accrued-blocks="Обновить общее количество накопленных блоков региона для игрока." + reload="Перезагрузить настройки GriefDefender." + schematic="Управление резервными копиями региона. Воспользуйтесь '/claimschematic create <name>', чтобы создать резервную копию региона." + sell-blocks="Продать блоки региона за серверную валюту.\nПримечание: требует плагин на экономику." + sell-claim="Выставить ваш регион на продажу. Воспользуйтесь /claimsell amount.\nПримечание: требует плагин на экономику." + town-chat="Переключить чат города." + town-tag="Установить собственный тег для города." + trust-group="Выдать группе доступ к вашему региону.\nДоступ: доступ к взаимодействию со всем, кроме инвентарей.\nКонтейнеры: доступ к взаимодействию со всем, включая инвентари.\nСтроительство: доступ ко всему вышеперечисленному плюс установка и поломка блоков.\nУправление: доступ ко всему вышеперечисленному плюс возможность изменять настройки региона." + trust-group-all="Выдать группе доступ ко ВСЕМ вашим регионам.\nДоступ: доступ к взаимодействию со всем, кроме инвентарей.\nКонтейнеры: доступ к взаимодействию со всем, включая инвентари.\nСтроительство: доступ ко всему вышеперечисленному плюс установка и поломка блоков.\nУправление: доступ ко всему вышеперечисленному плюс возможность изменять настройки региона." + trust-player="Выдать игроку доступ к вашему региону.\nДоступ: доступ к взаимодействию со всем, кроме инвентарей.\nКонтейнеры: доступ к взаимодействию со всем, включая инвентари.\nСтроительство: доступ ко всему вышеперечисленному плюс установка и поломка блоков.\nУправление: доступ ко всему вышеперечисленному плюс возможность изменять настройки региона." + trust-player-all="Выдать игроку доступ ко ВСЕМ вашим регионам.\nДоступ: доступ к взаимодействию со всем, кроме инвентарей.\nКонтейнеры: доступ к взаимодействию со всем, включая инвентари.\nСтроительство: доступ ко всему вышеперечисленному плюс установка и поломка блоков.\nУправление: доступ ко всему вышеперечисленному плюс возможность изменять настройки региона." + untrust-group="Отозвать доступ группы к вашему региону." + untrust-group-all="Отозвать доступ группы ко ВСЕМ вашим регионам." + untrust-player="Отозвать доступ игрока к вашему региону." + untrust-player-all="Отозвать доступ игрока ко ВСЕМ вашим регионам." + version="Вывести информацию о версии GriefDefender." + } + messages { + abandon-all-warning="&6Вы уверены, что хотите удалить &cВСЕ&6 ваши регионы?" + abandon-claim-delay-warning="&aЭтот регион был создан недавно и не может быть удалён до &6{date}&a." + abandon-claim-failed="&aНе удалось удалить регион. Результат действия: &f{result}&a." + abandon-claim-missing="&cНе найдено ни одного региона. Войдите в регион, который вы хотите удалить, или воспользуйтесь &f/abandonall&c." + abandon-other-success="&aРегион игрока &6{player}&a удалён. Теперь &6{player}&a имеет &6{amount}&a доступных для занятия блоков." + abandon-success="&aРегион удалён. Теперь у вас есть &6{amount}&a доступных для занятия блоков." + abandon-top-level="&cРегион не может быть удалён, поскольку в нём есть один или несколько суб-регионов. Чтобы удалить регион вместе с суб-регионами, воспользуйтесь &f/abandontop&c." + abandon-town-children="&cУ вас нет разрешения на удаление города, в котором есть суб-регионы, не принадлежащие вам. Воспользуйтесь &f/ignoreclaims&c или попросите владельцев суб-регионов удалить их. Если вы хотите удалить город, но не суб-регионы, воспользуйтесь &f/abandon&c." + abandon-warning="&6Вы уверены, что хотите удалить этот регион? Он больше не будет защищён." + adjust-accrued-blocks-success="&aКоличество накопленных блоков региона для &6{player}&a изменено на &6{amount}&a. Новое количество накопленных блоков: &6{total}&a." + adjust-bonus-blocks-success="&aКоличество бонусных блоков региона для &6{player}&a изменено на &6{amount}&a. Новое количество бонусных блоков: &6{total}&a." + bank-click-view-transactions="Нажмите здесь, чтобы просмотреть историю банковских переводов" + bank-deposit="&aУспешно передано &6{amount}&a в банк." + bank-deposit-no-funds="&cУ вас недостаточно средств для взноса в банк." + bank-info="&aБаланс: &6{balance}&a\nНалог: &6{tax-amount}&f. Срок сдачи &7{time-remaining}&a\nЗадолженность: &6{tax-balance}." + bank-no-permission="&cУ вас нет разрешения от пользователя &6{player}&c на управление банком этого региона." + bank-tax-system-disabled="&cСистема банков/налогов выключена. Если вы хотите её включить - измените значение поля 'bank-tax-system' в настроечном файле на true." + bank-title-transactions="Банковские переводы" + bank-withdraw="&aУспешно списано &6{amount}&a из банка." + bank-withdraw-no-funds="&cНа счету банка региона сейчас &a{balance}&c, поэтому с него нельзя списать &a{amount}&c." + block-claimed="&aЭтот блок занят пользователем &6{player}&a." + block-not-claimed="&cЭтот блок никем не занят." + block-sale-value="&aКаждый блок региона стоит &6{amount}&a. Вы можете продать &6{total}&a блоков." + claim-above-level="&cВы не можете занять этот блок, потому что он выше лимита по уровню региона - &a{limit}&c." + claim-action-not-available="&cЭто действие недоступно в регионах вида {type}&c." + claim-automatic-notification="&cЭтот сундук и блоки поблизости защищёны от поломки и ограбления." + claim-below-level="&cВы не можете занять этот блок, потому что он ниже лимита по уровню региона - &a{limit}&c." + claim-chest-confirmation="&cЭтот сундук защищён." + claim-chest-outside-level="&cЭтот сундук нельзя защитить, потому что он находится вне доступных вам границ по уровню региона - &a{min-level}&c и &a{max-level}&c." + claim-children-warning="&6У данного региона есть суб-регионы. Если вы уверены, что хотите удалить его, используйте &f/deleteclaim&6 ещё раз." + claim-context-not-found="&cКонтекст &f{context}&c не найден." + claim-disabled-world="&cВ этом мире нельзя создать регион." + claim-expired-inactivity="&cРегион игрока &6{player} с id &f{uuid}&c был удалён ввиду отсутствия активности." + claim-farewell="&aСообщение при выходе из региона установлено: &f{farewell}&a." + claim-farewell-clear="&aСообщение при выходе из региона удалено." + claim-farewell-invalid="&cФлаг региона &f{flag}&c не существует." + claim-greeting="&aПриветствие при входе в регион установлено: &f{greeting}&a." + claim-greeting-clear="&aПриветствие при входе в регион удалено." + claim-ignore="&aИгнорирование регионов включено." + claim-last-active="&aВ последний раз активность в этой области зарегистрирована &6{date}&a." + claim-mode-start="{type}&a угол выбран! Нажмите ПКМ ещё раз на противоположный угол, чтобы создать прямоугольный регион. Для отмены и выхода из режима наберите &f/claim&a." + claim-name="&aУстановлено имя региона: &6{name}&a." + claim-no-claims="&cУ вас нет регионов." + claim-no-set-home="&cВы должны быть вписаны в регион, чтобы использовать /sethome." + claim-not-found="&cЗдесь нет ни одного региона." + claim-not-yours="&cЭто не ваш регион." + claim-owner-already="&cВы уже являетесь владельцем этого региона." + claim-owner-only="&cТолько &6{player}&c может редактировать этот регион." + claim-protected-entity="&cЭто принадлежит игроку &6{player}&c." + claim-respecting="&aИгнорирование регионов выключено." + claim-restore-success="&aРегион успешно восстановлен." + claim-show-nearby="&aПоблизости найдено &6{amount}&a регионов." + claim-size-max="&cРазмер региона по оси &6{axis}&c &a({size})&c больше максимального допустимого размера &a{max-size}&c.\nОбласть должна быть минимум &a{min-area}&c и максимум &a{max-area}." + claim-size-min="&cРазмер региона по оси &6{axis}&c &a({size})&c меньше минимального допустимого размера &a{min-size}&c.\nОбласть должна быть минимум &a{min-area}&c и максимум &a{max-area}." + claim-size-need-blocks-2d="&cУ вас недостаточно блоков, чтобы создать регион этого размера.\nВам нужно ещё &a{block-amount}&c блоков." + claim-size-need-blocks-3d="&cУ вас недостаточно блоков, чтобы создать регион этого размера.\nВам нужно ещё &a{chunk-amount}&c чанков. &f({block-amount} блоков)" + claim-size-too-small="&cРазмер выделенной области - &a{width}&fx&a{length}&c - слишком мал. Регион должен иметь размер как минимум &a{min-width}&fx&a{min-length}&c." + claim-start="&aУгол региона вида &f{type}&a установлен! Используйте лопату на противоположном углу, чтобы создать прямоугольный регион. Для отмены уберите лопату." + claim-too-far="&cСлишком далеко." + claim-transfer-exceeds-limit="&cНе удалось передать регион - у нового владельца слишком много регионов." + claim-transfer-success="&aРегион передан." + claim-type-not-found="&cРегионов вида &f{type}&c не найдено." + claiminfo-ui-admin-settings="Администраторские настройки" + claiminfo-ui-bank-info="Банковская информация" + claiminfo-ui-claim-expiration="Истечение срока действия региона" + claiminfo-ui-click-admin="Нажмите, чтобы отобразить администраторские настройки" + claiminfo-ui-click-bank="Нажмите, чтобы проверить банковскую информацию" + claiminfo-ui-click-change-claim="Нажмите, чтобы изменить вид региона на {type}" + claiminfo-ui-click-toggle="Нажмите, чтобы переключить значение" + claiminfo-ui-deny-messages="Сообщения об отклонении" + claiminfo-ui-flag-overrides="Переопределения флагов" + claiminfo-ui-for-sale=Продаётся + claiminfo-ui-inherit-parent="Наследуется от родителя" + claiminfo-ui-last-active="Последняя активность" + claiminfo-ui-north-corners="Северные углы" + claiminfo-ui-pvp-override="Переопределение PvP" + claiminfo-ui-requires-claim-blocks="Требует блоки региона" + claiminfo-ui-return-bankinfo="Вернуться к банковской информации" + claiminfo-ui-return-claiminfo="Вернуться к информации о регионе" + claiminfo-ui-return-settings="Вернуться к стандартным настройкам" + claiminfo-ui-size-restrictions="Ограничения по размеру" + claiminfo-ui-south-corners="Южные углы" + claiminfo-ui-teleport-direction="Нажмите, чтобы телепортироваться на {direction}&f угол региона" + claiminfo-ui-teleport-feature="У вас нет разрешения на использование функции телепортации в этом регионе" + claiminfo-ui-teleport-spawn="Нажмите, чтобы телепортироваться на точку возрождения в регионе" + claiminfo-ui-title-claiminfo="Информация о регионе" + claiminfo-ui-town-settings="Настройки города" + claimlist-ui-click-info="Нажмите, чтобы увидеть больше информации" + claimlist-ui-click-purchase="Нажмите, чтобы приобрести регион" + claimlist-ui-click-teleport-target="Нажмите, чтобы телепортироваться к {name}&f {target}&f в &6{world}" + claimlist-ui-click-toggle-value="Нажмите, чтобы переключить значение вида {type}" + claimlist-ui-click-view-children="Нажмите, чтобы отобразить список регионов" + claimlist-ui-click-view-claims="Нажмите, чтобы отобразить ваши регионы" + claimlist-ui-no-claims-found="В мире регионов не найдено." + claimlist-ui-return-claimlist="Вернуться к списку регионов" + claimlist-ui-title="Список регионов" + claimlist-ui-title-child-claims=Суб-регионы + command-blocked="&cИспользование команды &f{command}&c заблокировано игроком &6{player}&c, владельцем региона." + command-claimban-success-block="&aБлок с id &c{id}&a успешно &cЗАБАНЕН&a." + command-claimban-success-entity="&aСущность с id &c{id}&a успешно &cЗАБАНЕНА&a." + command-claimban-success-item="&aПредмет с id &c{id}&a успешно &cЗАБАНЕН&a." + command-claimbuy-title="&bРегионы в продаже" + command-claimclear-killed="&cУбито &6{amount}&a сущностей вида {type}&f." + command-claimclear-no-entities="&cНе найдено ни одной сущности вида {type}&c." + command-claimclear-uuid-deny="&cТолько администраторы могут удалять регионы при помощи UUID." + command-claimflagdebug-disabled="Отладка флагов региона &cВЫКЛЮЧЕНА" + command-claimflagdebug-enabled="Отладка флагов региона &aВКЛЮЧЕНА" + command-claiminfo-not-found="&cНе найдено валидных UUID игрока или региона." + command-claiminfo-uuid-required="&cПри запуске не от имени игрока требуется UUID региона ." + command-claiminherit-disabled="Наследование от родителя &cВЫКЛЮЧЕНО" + command-claiminherit-enabled="Наследование от родителя &aВКЛЮЧЕНО" + command-claimspawn-not-found="&aНе удалось найти регион с именем {name}&a." + command-claimunban-success-block="&aБлок с id &6{id}&a успешно &6РАЗБАНЕН&a." + command-claimunban-success-entity="&aСущность с id &6{id}&a успешно &6РАЗБАНЕНА&a." + command-claimunban-success-item="&aПредмет с id &6{id}&a успешно &6РАЗБАНЕН&a." + command-cuboid-disabled="&aВключён режим создания &d2D&a-регионов." + command-cuboid-enabled="&aВключён режим создания &d3D&a-регионов." + command-execute-failed="&cНе удалось выполнить команду '{command} {args}'." + command-giveblocks-confirmation="&6Вы уверены, что хотите отдать игроку {player}&6 {amount}&6 блоков региона?" + command-giveblocks-confirmed="&aПередача блоков региона завершена." + command-giveblocks-not-enough="&cНе достаточно блоков региона! У вас есть только {amount}&c блоков региона, доступных к передаче.\n&bПримечание&f: Это количество не включает начальные блоки региона. Только накопленные и бонусные." + command-giveblocks-received="&aВам передано {amount}&a блоков региона от {player}&a." + command-inherit-only-child="&cЭту команду можно использовать только в суб-регионах." + command-invalid="&cКоманда не найдена." + command-invalid-amount="&cВведено неверное количество: &6{amount}&c." + command-invalid-claim="&cЭту команду нельзя использовать в регионах вида &f{type}&c." + command-invalid-group="&cГруппа &6{group}&c не существует." + command-invalid-player="&cИгрок &6{player}&c не найден." + command-invalid-player-group="&cНе является игроком или группой." + command-invalid-type="&cТип {type}&c не найден." + command-not-available-economy="&cЭта команда не доступна, пока сервер находится в режиме экономики." + command-option-exceeds-admin="&cЗначение опции &a'{value}&c' выходит за установленное администратором - '&a{admin-value}&c'. Применяю установленное администратором значение..." + command-pet-confirmation="&aПитомец передан." + command-pet-invalid="&cПередача питомца вида {type} не поддерживается." + command-pet-transfer-cancel="&aПередача питомца отменена." + command-pet-transfer-ready="&aГотов к передаче! Нажмите на питомца, которого хотите передать, или нажмите ЛКМ для отмены." + command-player-not-found="&cИгрок '&6{player}&c' не найден." + command-world-not-found="&cМир '&6{world}&c' не найден." + command-worldedit-missing="&cДля использования этой команды на сервер должен быть установлен WorldEdit." + create-cancel="&cСоздание региона отменено." + create-cuboid-disabled="&cВозможность создавать регионы в форме &d3D&c-кубоида отключено администратором.\nВы можете создавать регионы в форме &d3D&c-кубоида, только если вы являетесь администратором или поверх вашего &d2D&c-региона." + create-failed-claim-limit="&cВы достигли ограничения в &a{limit}&c по количеству регионов вида &a{type}&c. Используйте &f/abandon&c, чтобы удалить существующий регион." + create-failed-result="&aНе удалось создать регион: &6{reason}&a." + create-insufficient-blocks-2d="&cУ вас недостаточно блоков для создания региона.\nВам нужно ещё &a{block-amount}&c блоков." + create-insufficient-blocks-3d="&cУ вас недостаточно блоков для создания региона.\nВам нужно ещё &a{chunk-amount}&c чанков. &f({block-amount} блоков)" + create-overlap="&cВы не можете создать здесь регион, потому что он будет пересекаться с другим вашим регионом. Воспользуйтесь &f/abandonclaim&c, чтобы удалить его, или используйте лопату на его угол, чтобы изменить его размер." + create-overlap-player="&cВы не можете создать здесь регион, потому что он будет пересекаться с регионом, принадлежащим игроку &6{player}&c." + create-overlap-short="&cВыбранная область пересекается с существующим регионом." + create-subdivision-fail="&cВ выбранной точке нет ни одного региона. Пожалуйста, кликните по блоку, находящемуся в родительском регионе, чтобы создать суб-регион." + create-subdivision-only="&cНе удалось создать регион. Только суб-регионы могут быть объёмом в один блок." + create-success="{type}&a регион создан! Используйте &f/trust&a, чтобы впустить в него друзей." + debug-error-upload="&cОшибка загрузки контента {content}&c." + debug-no-records="&cНет отладочной записи!" + debug-paste-success="&aВставка успешна!" + debug-record-end="Запись закончена" + debug-record-start="Запись начата" + debug-time-elapsed="Затраченное время" + delete-all-player-failed="&aНе удалось удалить все регионы, принадлежащие игроку &6{player}&a. Результат действия: &f{result}&a." + delete-all-player-success="&aУдалены все регионы, принадлежавшие игроку &6{player}&a." + delete-all-player-warning="&6Вы уверенны, что хотите удалить все регионы, принадлежащие игроку &a{player}&6?" + delete-all-type-deny="&cНе удалось удалить все регионы вида &6{type}&c. Плагин не разрешил." + delete-all-type-success="&cВсе регионы вида &6{type}&c удалены." + delete-all-type-warning="&6Вы уверены, что хотите удалить все регионы вида &c{type}&6?" + delete-claim-failed="&aНе удалось удалить регион. Результат действия: &f{result}&a." + delete-claim-success="&aУспешно удалён регион, принадлежавший игроку &f{player}&a." + delete-claim-warning="&6Вы уверены, что хотите удалить регион, принадлежащий игроку &f{player}&6?" + economy-balance="&aВаш новый баланс: &6{balance}&a." + economy-block-available-purchase-2d="&aУ вас достаточно средств, чтобы создать регион ещё на &6{block-amount}&a блоков." + economy-block-available-purchase-3d="&aУ вас достаточно средств, чтобы создать регион ещё на &6{chunk-amount}&a чанков. &f({block-amount} блоков)" + economy-block-buy-invalid="&cКоличество блоков должно быть больше 0." + economy-block-buy-sell-disabled="&cПросим прощения, но покупка и продажа блоков региона отключена." + economy-block-cost="&aКаждый блок региона стоит &6{amount}&a." + economy-block-not-available="&cУ вас недостаточно блоков региона для продажи." + economy-block-only-buy="&cБлоки региона можно только купить, но не продать." + economy-block-only-sell="&cБлоки региона можно только продать, но не купить." + economy-block-purchase-confirmation="&aС вашего счёта списано &6{amount}&a. Теперь у вас доступно &6{balance}&a блоков региона." + economy-block-purchase-cost="&aКаждый блок региона стоит &6{amount}&a. Ваш баланс: &6{balance}&a." + economy-block-purchase-limit="&cНовое количество доступных блоков региона &a{total}&c превышает ограничение в &a{limit}&c. Операция отменена." + economy-block-sale-confirmation="&aНа ваш счёт начислено &6{deposit}&a. Теперь у вас доступно &6{amount}&a блоков региона." + economy-block-sell-error="&cНе удалось продать блоки. Причина: &f{reason}&c." + economy-claim-abandon-success="&aРегион(ы) удалены. Вам возмещены средства в размере '&6{amount}&a'." + economy-claim-buy-cancelled="&cПокупка отменена! Не удалось купить регион, принадлежащий &6{player}&c. Результат действия: &a{result}" + economy-claim-buy-confirmation="&6Вы уверены, что хотите купить этот регион за &a{amount}&6? Нажмите &aПодтвердить&6 для продолжения." + economy-claim-buy-confirmed="&aВы успешно приобрели регион за &6{amount}&a." + economy-claim-buy-not-enough-funds="&cУ вас недостаточно средств, чтобы приобрести регион за &a{amount}&c. На вашем счету сейчас &a{balance}&c и для покупки нужно ещё &a{amount_required}&c." + economy-claim-buy-transfer-cancelled="&cПередача региона отменена! Не удалось передать регион от &6{owner}&c игроку &6{player}&c. Результат действия: &a{result}" + economy-claim-not-for-sale="&cЭтот регион не продаётся." + economy-claim-sale-cancelled="&aВы отменили продажу вашего региона." + economy-claim-sale-confirmation="&6Вы уверены, что хотите продать свой регион за &a{amount}&6? Если регион купят, покупателю перейдут все находящиеся в нём блоки и предметы. Нажмите Подтвердить, если вы согласны с этим." + economy-claim-sale-confirmed="&aРегион выставлен на продажу по стоимости &6{amount}&a." + economy-claim-sale-invalid-price="&cСтоимость региона &a{amount}&c должна быть больше или равна &a0&c." + economy-claim-sold="&aВаш регион продан! &6{amount}&a было записано на ваш счёт. Ваш текущий баланс: &6{balance}&a." + economy-mode-block-sale-confirmation="&aНа ваш счёт зачислено &6{deposit}&a. Ваш текущий баланс: &6{balance}&a. Теперь у вас достаточно средств, чтобы создать регион ещё на &6{amount}&a блоков." + economy-mode-resize-success-2d="&aРазмер региона изменён. Ваш новый баланс: &6{balance}&a. У вас достаточно средств, чтобы создать регион ещё на &6{block-amount}&a блоков." + economy-mode-resize-success-3d="&aРазмер региона изменён. Ваш новый баланс: &6{balance}&a. У вас достаточно средств, чтобы создать регион ещё на &6{chunk-amount}&a чанков. &f({block-amount} блоков)" + economy-not-enough-funds="&cУ вас недостаточно средств на покупку этого региона. Ваш текущий баланс: '&a{balance}&c', а для покупки необходимо '&a{amount}&c'." + economy-not-installed="&cПлагин на экономику не установлен!" + economy-player-not-found="&cСчёт пользователя &6{player}&c не найден." + economy-remaining-funds="&aДля создания регионов вам доступно &6{amount}&a." + economy-virtual-not-supported="&cПлагин на экономику не поддерживает виртуальные счета. Воспользуйтесь другим плагином или свяжитесь с разработчиком вашего плагина, чтобы он добавил в него поддержку виртуальных счетов." + economy-withdraw-error="&cНе удалось списать средства: &f{reason}&c." + feature-not-available="&cДанный функционал не закончен и будет доступен в новой версии." + flag-description-custom-block-break="Управляет возможностью ломать блоки." + flag-description-custom-block-grow="Управляет возможностью блоков к росту." + flag-description-custom-block-place="Управляет возможностью установки блоков." + flag-description-custom-block-spread="Управляет возможностью блока распространяться." + flag-description-custom-build="Управляет возможностью совершения действий с блоками и сущностями, таких, как поломка, установка и взаимодействие." + flag-description-custom-enderpearl="Управляет возможностью пользоваться Жемчугом Края." + flag-description-custom-exit-player="Управляет возможностью игрока выйти из региона." + flag-description-custom-exp-drop="Управляет возможностью выпадения сфер опыта." + flag-description-custom-explosion-block="Управляет возможностью взрывов влиять на блоки." + flag-description-custom-explosion-entity="Управляет возможностью взрывов влиять на сущности." + flag-description-custom-fall-damage="Управляет возможностью игроков получать урон от падения." + flag-description-custom-interact-block="Управляет возможностью игроков взаимодействовать с блоками.\n&bПримечание&f: сюда не входят блоки с инвентарём, такие, как сундуки." + flag-description-custom-interact-entity="Управляет возможностью игроков взаимодействовать с сущностями.\n&bПримечание&f: сюда не входит доступ к сущностям с инвентарём, таким, как лошади." + flag-description-custom-interact-inventory="Управляет возможностью игроков взаимодействовать с инвентарями." + flag-description-custom-invincible="Управляет неуязвимостью игроков." + flag-description-custom-item-drop="Управляет возможностью игроков выбрасывать предметы." + flag-description-custom-item-pickup="Управляет возможностью игроков подбирать предметы." + flag-description-custom-monster-damage="Управляет возможностью монстров наносить урон." + flag-description-custom-pistons="Управляет возможностью использования поршней." + flag-description-custom-portal-use="Управляет возможностью использования порталов." + flag-description-custom-spawn-ambient="Управляет возможностью появления мобов окружения, таких, как летучие мыши." + flag-description-custom-spawn-animal="Управляет возможностью появления животных, таких, как коровы и свиньи." + flag-description-custom-spawn-aquatic="Управляет возможностью появления подводных мобов, таких, как спруты и стражи." + flag-description-custom-spawn-monster="Управляет возможностью появления враждебных мобов, таких, как криперы и скелеты." + flag-description-custom-teleport-from="Управляет возможностью игроков телепортироваться из региона." + flag-description-custom-teleport-to="Управляет возможностью игроков телепортироваться в регион." + flag-description-custom-use="Управляет возможностью игроков пользоваться блоками без инвентаря." + flag-description-custom-vehicle-destroy="Управляет возможностью поломки транспорта." + flag-description-custom-wither-damage="Управляет возможностью Иссушителя наносить урон." + flag-description-block-break="Управляет возможностью сломать блок.\n&dПример&f : чтобы запретить любому источнику ломать блоки земли, введите\n&a/cf block-break minecraft:dirt false\n&bПримечание&f : minecraft - это id мода, а dirt - id блока.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-block-grow="Управляет возможностью блока вырастать.\n&dПример&f : чтобы запретить кактусу расти, введите\n&a/cf block-grow minecraft:cactus false\n&bПримечание&f : minecraft - это id мода, а cactus - id блока.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-block-modify="Управляет возможностью изменения блоков.\n&dПример&f : чтобы запретить любому источнику поджигать блок, введите\n&a/cf block-modify minecraft:fire false\n&bПримечание&f : minecraft - это id мода, а fire - id блока.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-block-place="Управляет возможностью поставить блок.\n&dПример&f : чтобы запретить любому источнику ставить землю, введите\n&a/cf block-place minecraft:dirt false\n&bПримечание&f : minecraft - это id мода, а dirt - id блока.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-block-spread="Управляет возможностью блока распространяться на другие блоки.\n&dПример&f : чтобы запретить распространение огня, введите\n&a/cf block-spread any false context[source=fire]\n&bПримечание&f : 'any' обозначает любой целевой блок, а fire - id источника.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-collide-block="Управляет возможностью столкновения сущности с блоком.\n&dПример&f : чтобы запретить сущностям сталкиваться с каменными нажимными плитами, введите\n&a/cf collide-block minecraft:stone_pressure_plate false\n&bПримечание&f : minecraft - это id мода, а stone_pressure_plate - id блока.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-collide-entity="Управляет возможностью столкновения сущности с другой сущностью.\n&dПример&f : чтобы отключить столкновение сущностей с рамками, введите\n&a/cf collide-entity minecraft:item_frame false\n&bПримечание&f : minecraft - это id мода, а item_frame - id сущности.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-command-execute="Управляет возможностью выполнять команды.\n&dПример&f : чтобы запретить команду '/shop select' из Pixelmon, введите\n&a/cf command-execute pixelmon:shop[select] false\n&bПримечание&f : &o&6pixelmon&f - это id мода, &o&6shop&f - базовая команда, а &o&6select&f - аргумент.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-command-execute-pvp="Управляет возможностью выполнять команды во время сражения с другим игроком.\n&dПример&f : чтобы запретить команду '/shop select' из Pixelmon, введите \n&a/cf command-execute-pvp pixelmon:shop[select] false\n&bПримечание&f : &o&6pixelmon&f - это id модв, &o&6shop&f - базовая команда, а &o&6select&f - аргумент.\nЕсли id мода не указан - будет использоваться minecraft." + flag-description-enter-claim="Управляет возможностью входа сущности в регион.\n&dПример&f : чтобы запретить игрокам входить в регион, введите\n&a/cf enter-claim player false\n&bПримечание&f : если вы хотите изменить разрешение для групп, используйте команду /cfg." + flag-description-entity-chunk-spawn="Управляет возможностью появления сохранённой сущности при загрузке чанка.\n&dПример&f : чтобы запретить лошадям появляться при загрузке чанка, введите\n&a/cf entity-chunk-spawn minecraft:horse false\n&bПримечание&f : это удалит &cВСЕ&f сохранённые сущности, когда чанк будет загружен. Если чанк уже загружен - сущности будут удалены при его перезагрузке. Используйте с большой осторожностью." + flag-description-entity-damage="Управляет возможностью сущности получить урон.\n&dПример&f : чтобы запретить животным получать урон, введите\n&a/cf entity-damage minecraft:animal false." + flag-description-entity-riding="Управляет возможностью сесть на сущность верхом.\n&dПример&f : чтобы запретить садиться верхом на лошадей, введите\n&a/cf entity-riding minecraft:horse false." + flag-description-entity-spawn="Управляет возможностью сущности появиться в мире.\n&dПример&f : чтобы запретить генерацию свиней, введите\n&a/cf entity-spawn minecraft:pig false\n&bПримечание&f : сюда не входят предметы. См. флаг item-spawn." + flag-description-entity-teleport-from="Управляет возможностью сущности телепортироваться из региона.\n&dПример&f : чтобы запретить игрокам телепортироваться из этого региона, введите\n&a/cf entity-teleport-from player false\n&bПримечание&f : если вы хотите изменить разрешение для групп, используйте команду /cfg." + flag-description-entity-teleport-to="Управляет возможностью сущности телепортироваться в регион.\n&dПример&f : чтобы запретить игрокам телепортироваться в этот регион, введите\n&a/cf entity-teleport-to player false\n&bПримечание&f : если вы хотите изменить разрешение для групп, используйте команду /cfg." + flag-description-exit-claim="Управляет возможностью сущности выйти из региона.\n&dПример&f : чтобы запретить игрокам выход из региона, введите\n&a/cf exit-claim player false\n&bПримечание&f : если вы хотите изменить разрешение для групп, используйте команду /cfg." + flag-description-explosion-block="Управляет возможностью взрыва повреждать блоки в регионе.\n&dПример&f : чтобы запретить взрыву влиять на любые блоки, введите\n&a/cf explosion-block any false" + flag-description-explosion-entity="Управляет возможностью взрыва наносить урон сущностям в регионе.\n&dПример&f : чтобы запретить взрыву влиять на любые сущности, введите\n&a/cf explosion-entity any false" + flag-description-interact-block-primary="Управляет возможностью игрока нажимать ЛКМ (атаковать) по блоку.\n&dПример&f : чтобы запретить игрокам нажимать ЛКМ по сундукам, введите\n&a/cf interact-block-primary minecraft:chest false" + flag-description-interact-block-secondary="Управляет возможностью игрока нажимать ПКМ по блоку.\n&dПример&f : чтобы запретить игрокам нажимать ПКМ (открывать) сундуки, введите\n&a/cf interact-block-secondary minecraft:chest false" + flag-description-interact-entity-primary="Управляет возможностью игрока нажимать ЛКМ (атаковать) по сущности.\n&dПример&f : чтобы запретить игрокам нажимать ЛКМ по коровам, введите\n&a/cf interact-entity-primary minecraft:cow false" + flag-description-interact-entity-secondary="Управляет возможностью игрока нажимать ПКМ по сущности.\n&dПример&f : чтобы запретить игрокам взаимодействовать с жителями, введите\n&a/cf interact-entity-secondary minecraft:villager false" + flag-description-interact-inventory="Управляет возможностью игрока нажимать ПКМ (открывать) блок, содержащий инвентарь, например - сундук.\n&dПример&f : чтобы запретить игрокам открывать сундуки, введите\n&a/cf interact-inventory minecraft:chest false" + flag-description-interact-inventory-click="Управляет возможностью игрока кликать по слоту инвентаря.\n&dПример&f : чтобы запретить игрокам кликать по слоту инвентаря, в котором лежит алмаз, введите\n&a/cf interact-inventory-click minecraft:diamond false" + flag-description-interact-item-primary="Управляет возможностью игрока нажимать ЛКМ (атаковать) предметом.\n&dПример&f : чтобы запретить игрокам атаковать алмазным мечом, введите\n&a/cf interact-item-primary minecraft:diamond_sword false" + flag-description-interact-item-secondary="Управляет возможностью игрока нажимать ПКМ предметом.\n&dПример&f : чтобы запретить игрокам нажимать ПКМ огнивом, введите\n&a/cf interact-item-secondary minecraft:flint_and_steel false" + flag-description-item-drop="Управляет возможностью предмета быть выброшенным в регионе.\n&dПример&f : чтобы запретить игрокам выбрасывать алмазы в этом регионе, введите\n&a/cf item-drop minecraft:flint_and_steel false context[source=player]" + flag-description-item-pickup="Управляет возможностью предмета быть подобранным в регионе.\n&dПример&f : чтобы запретить игрокам поднимать алмазы, введите\n&a/cf item-pickup minecraft:diamond false" + flag-description-item-spawn="Управляет возможностью предмета появиться в регионе.\n&dПример&f : чтобы запретить перьям появляться в этом регионе, введите\n&a/cf item-spawn minecraft:feather false" + flag-description-item-use="Управляет возможностью воспользоваться предметом.\n&dПример&f : чтобы запретить есть яблоки в этом регионе, введите\n&a/cf item-use minecraft:apple false" + flag-description-leaf-decay="Управляет возможностью листьев опадать в этом регионе.\n&dПример&f : чтобы запретить листьям опадать в этом регионе, введите\n&a/cf leaf-decay any false" + flag-description-liquid-flow="Управляет возможностью жидкости, такой, как лава или вода, растекаться в этом регионе.\n&dПример&f : чтобы запретить всем видам жидкости растекаться в этом регионе, введите\n&a/cf liquid-flow any false" + flag-description-portal-use="Управляет возможностью пользоваться порталом.\n&dПример&f : чтобы запретить только игрокам проходить в порталы без влияния на не-игроков, введите\n&a/cf portal-use any false context[source=player]" + flag-description-projectile-impact-block="Управляет возможностью снаряда попадать (сталкиваться) с блоком.\n&dПример&f : чтобы запретить покеболам из Pixelmon сталкиваться с блоками, введите\n&a/cf projectile-impact-block any false[source=pixelmon:occupiedpokeball]\n&bПримечание&f : сюда входят такие вещи, как зелья, стрелы, снежки, покеболы из Pixelmon и т.д." + flag-description-projectile-impact-entity="Управляет возможностью снаряда попадать (сталкиваться) с сущностью.\n&dПример&f : чтобы запретить стрелам попадать в сущности, введите\n&a/cf projectile-impact-entity minecraft:arrow false[source=player]\n&bПримечание&f : сюда входят такие вещи, как зелья, стрелы, снежки, покеболы из Pixelmon и т.д." + flag-description-custom-chest-access="Управляет возможностью игрока взаимодействовать с инвентарями сундуков." + flag-description-custom-chorus-fruit-teleport="Управляет возможностью игрока телепортироваться при помощи плодов коруса." + flag-description-custom-crop-growth="Управляет возможностью культур расти." + flag-description-custom-damage-animals="Управляет возможностью животных получать урон." + flag-description-custom-enderman-grief="Управляет возможностью Странников Края ломать и ставить блоки." + flag-description-custom-enter-player="Управляет возможностью игрока входить в этот регион." + flag-description-custom-explosion-creeper="Управляет возможностью крипера взрываться." + flag-description-custom-explosion-tnt="Управляет возможностью динамита взрываться." + flag-description-custom-fire-damage="Управляет возможностью огня наносить урон." + flag-description-custom-fire-spread="Управляет возможностью огня распространяться." + flag-description-custom-grass-growth="Управляет возможностью травы расти." + flag-description-custom-ice-form="Управляет возможностью замерзания льда." + flag-description-custom-ice-melt="Управляет возможностью таяния льда." + flag-description-custom-lava-flow="Управляет возможностью лавы растекаться." + flag-description-custom-leaf-decay="Управляет возможностью листвы опадать." + flag-description-custom-lighter="Управляет возможностью игрока использовать огниво." + flag-description-custom-lightning="Управляет возможностью молнии наносить урон." + flag-description-custom-mushroom-growth="Управляет возможностью грибов вырастать." + flag-description-custom-mycelium-spread="Управляет возможностью мицелия распространяться." + flag-description-custom-pvp="Управляет возможностью сражений между игроками." + flag-description-custom-ride="Управляет возможностью садиться на транспорт (включая животных)." + flag-description-custom-sleep="Управляет возможностью игрока спать в кроватях." + flag-description-custom-snow-fall="Управляет возможностью появления снега." + flag-description-custom-snow-melt="Управляет возможностью таяния снега." + flag-description-custom-snowman-trail="Управляет возможностью снежных големов оставлять след из снега." + flag-description-custom-soil-dry="Управляет возможностью почвы высыхать." + flag-description-custom-vehicle-place="Управляет возможностью ставить транспорт (лодки, вагонетки и т.п.)." + flag-description-custom-vine-growth="Управляет возможностью роста лиан (и ламинарий)." + flag-description-custom-water-flow="Управляет возможностью воды растекаться." + flag-invalid-context="&cВведён неверный контекст '&f{context}&c' для базового флага &f{flag}&c." + flag-invalid-meta="&cВведены неверные метаданные цели '&f{value}&c' для базового флага &f{flag}&c." + flag-invalid-target="&cВведена неверная цель '&f{target}&c' для базового флага &f{flag}&c." + flag-not-found="&cФлаг &f{flag}&c не найден." + flag-not-set="&fФлаг &r{flag}&f не установлен.\nИспользуется стандартное значение &r{value}&f." + flag-overridden="&cНе удалось установить &f{flag}&c - он переопределён администратором." + flag-override-not-supported="&cРегион вида &f{type}&c не поддерживает переопределение флагов." + flag-reset-success="&aФлаги региона успешно откачены на стандартные значения." + flag-reset-warning="&6Вы уверены, что хотите откатить флаги этого региона на стандартные значения?" + flag-set-permission-target="&aРазрешение &b{permission}&a вида &f{type}&a с контекстами &7{contexts}&a установлено в значение &6{value}&a для &6{target}&a." + flag-ui-click-allow="Нажмите здесь, чтобы разрешить этот флаг." + flag-ui-click-deny="Нажмите здесь, чтобы запретить этот флаг." + flag-ui-click-remove="Нажмите здесь, чтобы удалить этот флаг." + flag-ui-click-toggle="&fНажмите здесь, чтобы переключить значение флага &r{flag}&f." + flag-ui-info-claim="Значения в регионе проверяются перед стандартными. Позволяет владельцам регионов определять значения флагов только для определённого региона." + flag-ui-info-default="Стандартные значения проверяются последними. Значения из переопределения и региона имеют высший приоритет." + flag-ui-info-inherit="Наследование - флаг, устанавливаемый родительским регионом, который не может быть изменён." + flag-ui-info-override="Переопределение имеет наивысший приоритет и проверяется до стандартных и регионных значений. Позволяет администраторам переопределять поведение всех базовых и администраторских регионов." + flag-ui-inherit-parent="Этот флаг унаследован от родителя {name}&f и &nне может&f быть изменён." + flag-ui-override-no-permission="Этот флаг переопределён администратором и &n&cНЕ&f может быть изменён." + flag-ui-override-permission="&fФлаг &r{flag}&f &cпереопределён&f администратором.\nНажмите здесь, чтобы удалить этот флаг." + flag-ui-return-flags="Вернуться к флагам" + label-accessors=Доступ + label-area=Площадь + label-blocks=Блоков + label-builders=Строительство + label-buy=Покупка + label-cancel=Отмена + label-children=суб-регионы + label-confirm=Подтвердить + label-containers=Контейнеры + label-context=Контекст + label-created=Создан + label-displaying=Отображение + label-expired=Истекло + label-farewell=Прощание + label-flag=Флаг + label-greeting=Приветствие + label-group=Группа + label-inherit=Наследование + label-location=Местоположение + label-managers=Управление + label-name=Имя + label-no=Нет + label-output=Вывод + label-owner=Владелец + label-permission=Разрешение + label-player=Игрок + label-price=Цена + label-raid=Рейды + label-resizable="Можно изменить размер" + label-result=Результат + label-schematic="Резервная копия" + label-source=Источник + label-spawn="Точка возрождения" + label-target=Цель + label-trust=Доступ + label-type=Тип + label-unknown=Неизвестно + label-user=Пользователь + label-world=Мир + label-yes=Да + mode-admin="&aВключён режим создания администраторских регионов. Все созданные регионы будут бесплатны и доступны для редактирования всеми администраторами." + mode-basic="&aВключён режим создания обычных областей." + mode-nature="&aГотов восстанавливать природу! ПКМ для начала, &f/modebasic&c - для выхода из режима." + mode-subdivision="&aВключён режим создания суб-регионов. Используйте лопату, чтобы создавать суб-регионы в существующих регионах. Воспользуйтесь &f/modebasic&a, чтобы выйти из этого режима." + mode-town="&aВключён режим создания городов." + option-description-abandon-delay="&aКоличество дней до того, как свежесозданный регион может быть удалён." + option-description-abandon-return-ratio="&aДоля базовых блоков региона, возвращаемых игроку при удалении региона." + option-description-blocks-accrued-per-hour="&aЗарабатываемые блоки в час.\n&dПримечание&f: См. /playerinfo." + option-description-chest-expiration="&aКоличество дней, после которых автоматически созданный регион вокруг сундука будет удалён.\n&dПримечание&f: по истечении срока регион может быть просто удалён или откачен на состояние при создании. Это зависит от настроек сервера. Свяжитесь с администратором для дополнительной информации." + option-description-create-limit="&aКоличество базовых регионов на игрока.\n&dПримечание&f: Значение ниже 0 убирает ограничение." + option-description-create-mode="&aРежим создания региона (Площадь = 2D, Объём = 3D).\n&dПримечание&f: &bПлощадь&a действует только по осям x и z.\n&bОбъём&a действует по осям x, y и z." + option-description-economy-block-cost="&aСтоимость одного блока региона.\n&dПримечание&f: Формула для вычисления стоимости региона: цена * количество блоков региона." + option-description-economy-block-sell-return="&aВозвращаемые средства при продаже блоков региона.\n&dПримечание&f: Формула для вычисления возвращаемых средств: цена * количество блоков региона." + option-description-expiration="&aКоличество дней без активности, после которого регион будет удалён.\n&dПримечание&f: по истечении срока регион может быть просто удалён или откачен на состояние при создании. Это зависит от настроек сервера. Свяжитесь с администратором для дополнительной информации." + option-description-initial-blocks="&aКоличество блоков, которое игрок имеет изначально." + option-description-max-accrued-blocks="&aОграничение на накопленные блоки (с течением времени).\n&dПримечание&f: не влияет на купленные или подаренные администратором блоки региона." + option-description-max-level="&aМаксимальный уровень по оси y, на котором может быть создан регион." + option-description-max-size-x="&aМаксимальный размер региона по оси x." + option-description-max-size-y="&aМаксимальный размер региона по оси y." + option-description-max-size-z="&aМаксимальный размер региона по оси z." + option-description-min-level="&aМинимальный уровень по оси y, на котором может быть создан регион." + option-description-min-size-x="&aМинимальный размер региона по оси x." + option-description-min-size-y="&aМинимальный размер региона по оси y." + option-description-min-size-z="&aМинимальный размер региона по оси z." + option-description-player-command="&aИспользуется для выполнения команды в определённом контексте." + option-description-player-deny-flight="&aОпределяет, может ли игрок летать в регионе.\n&dПримечание&f: не даёт игрокам возможность летать, только удаляет её при необходимости. Даёт наибольшую совместимость с другими плагинами." + option-description-player-deny-godmode="&aОпределяет, может ли игрок быть в режиме бога в регионе.\n&dПримечание&f: не даёт игрокам возможность входить в режим бога, только удаляет её при необходимости. Даёт наибольшую совместимость с другими плагинами." + option-description-player-deny-hunger="&aОпределяет, может ли игрок терять сытость в регионе.\n&dПримечание&f: не даёт игрокам получать сытость в регионе, только удаляет возможность её терять при необходимости. Даёт наибольшую совместимость с другими плагинами." + option-description-player-gamemode="&aОпределяет игровой режим для игроков в регионе." + option-description-player-health-regen="&aОпределяет скорость восстановления здоровья игроков в регионе.\n&dПримечание&f: не имеет эффекта на игроков с полным запасом здоровья. \n&dПримечание&f: значение &6-1&f отключает эту опцию." + option-description-player-keep-inventory="&aОпределяет, сохранит ли игрок содержимое своего инвентаря при смерти в регионе." + option-description-player-keep-level="&aОпределяет, сохранит ли игрок свой уровень при смерти в регионе." + option-description-player-walk-speed="&aОпределяет скорость ходьбы игроков в регионе.\n&dПримечание&f: значение &6-1&f отключает эту опцию." + option-description-player-weather="&aОпределяет погоду для игрока в регионе." + option-description-radius-inspect="&aРадиус в блоках для поиска регионов поблизости при инспектировании." + option-description-radius-list="&aРадиус в блоках для составления списка регионов поблизости." + option-description-tax-expiration="&aКоличество дней без оплаты налогов, после которых регион будет заморожен.\n&dПримечание&f: заморозка означает отсутствие доступа к строительству и инвентарям в регионе до совершения оплаты." + option-description-tax-expiration-days-keep="&aКоличество дней после заморозки региона до его удаления.\n&dПримечание&f: по истечении срока регион может быть просто удалён или откачен на состояние при создании. Это зависит от настроек сервера. Свяжитесь с администратором для дополнительной информации." + option-description-tax-rate="&aВеличина налога на регион.\n&dПримечание&f: величина налога вычисляется по размеру региона в блоках." + option-invalid-context="&cВведён неверный контекст '&f{context}&c' для опции &f{option}&c." + option-invalid-target="&cВведена неверная цель '&f{target}&c' для опции &f{option}&c." + option-invalid-value="&cВведено неверное значение '&6{value}&c' для опции &f{option}&c. This option only accepts &f{type}&c values." + option-not-found="&cОпция &f{option}&c не найдена." + option-not-set="&a{option}&f не установлена.\nПрименяется стандартное значение &a{value}&f." + option-override-not-supported="&cРегионы вида &f{type}&c не поддерживают переопределение опций." + option-player-deny-flight="&cУ вас нет разрешения на полёт в этом регионе. Вы были телепортированы на безопасное место на земле." + option-reset-success="&aОпции региона успешно откачены на стандартные значения." + option-set-target="&aОпция &b{option}&a вида &b{type}&a с контекстами &7{contexts}&a установлена в значение {value}&a для &6{target}&a." + option-ui-click-toggle="Нажмите здесь, чтобы переключить опцию &f{option}&r." + option-ui-inherit-parent="Эта опция унаследована от родительского региона {name}&f и &nне может&f быть изменена." + option-ui-overridden="&cНе удалось установить опцию &f{option}&c. Значение опции переопределено администратором." + option-ui-override-no-permission="Значение опции было переопределено администратором, она &n&cНЕ&f может быть изменена." + owner-admin=Администратор + permission-access="&cУ вас нет разрешения от игрока &6{player}&c на доступ к этому." + permission-assign-without-having="&cВы не можете выдать разрешение на то, что не разрешено вам." + permission-ban-block="&cБлок {id}&c &l&nЗАБАНЕН&c и не может быть использован." + permission-ban-entity="&cСущность {id}&c &l&nЗАБАНЕНА&c и не может быть использована." + permission-ban-item="&cПредмет {id}&c &l&nЗАБАНЕН&c и не может быть использован." + permission-build="&cУ вас нет разрешения от игрока &6{player}&c на строительство." + permission-build-near-claim="&cУ вас нет разрешения от игрока &6{player}&c на строительство около его региона." + permission-claim-create="&cУ вас нет разрешения на создание региона." + permission-claim-delete="&cУ вас нет разрешения на удаление регионов вида {type}&c." + permission-claim-enter="&cУ вас нет разрешения на вход в этот регион." + permission-claim-exit="&cУ вас нет разрешения на выход из этого региона." + permission-claim-ignore="&cУ вас нет разрешения на игнорирование регионов вида {type}&c." + permission-claim-list="&cУ вас нет разрешения на просмотр информации о регионах, принадлежащих другому игроку." + permission-claim-manage="&cУ вас нет разрешения на редактирование регионов вида {type}&c." + permission-claim-reset-flags="&cУ вас нет разрешения на откат значений флагов для региона вида {type}&c." + permission-claim-reset-flags-self="&cУ вас нет разрешения на откат значений флагов для вашего региона." + permission-claim-resize="&cУ вас нет разрешения на изменение размера этого региона." + permission-claim-sale="&cУ вас нет разрешения на продажу этого региона." + permission-claim-transfer-admin="&cУ вас нет разрешения на передачу администраторского региона." + permission-clear="&cРазрешения в этом регионе очищены. Чтобы изменить разрешения для ВСЕХ ваших регионов, выйдите из них наружу." + permission-clear-all="&cТолько владелец области может очистить все разрешения." + permission-command-trust="&cУ вас нет разрешения на выдачу этого вида разрешения." + permission-cuboid="&cУ вас нет разрешения на создание/изменение размера базовых регионов в 3D-режиме." + permission-edit-claim="&cУ вас нет разрешения на изменение этого региона." + permission-fire-spread="&cУ вас нет разрешения на поджигание блоков в этом регионе." + permission-flag-defaults="&cУ вас нет разрешения на изменение стандартных значений флагов." + permission-flag-overrides="&cУ вас нет разрешения на редактирование переопределённых значений флагов." + permission-flag-use="&cУ вас нет разрешения на использование этого флага." + permission-flow-liquid="&cУ вас нет разрешения на разливание жидкостей в этом регионе." + permission-global-option="&cУ вас нет разрешения на редактирование глобальных настроек." + permission-grant="&cУ вас нет разрешения на выдачу разрешения, которого у вас нет." + permission-group-option="&cУ вас нет разрешения на изменение групповых настроек." + permission-interact-block="&cУ вас нет разрешения от игрока &6{player}&c на взаимодействие с блоком &d{block}&c." + permission-interact-entity="&cУ вас нет разрешения от игрока &6{player}&c на взаимодействие с сущностью &d{entity}&c." + permission-interact-item="&cУ вас нет разрешения от игрока &6{player}&c на взаимодействие с предметом &d{item}&c." + permission-interact-item-block="&cУ вас нет разрешения на использование предмета &d{item}&c на блок &b{block}&c." + permission-interact-item-entity="&cУ вас нет разрешения на использование предмета &d{item}&c на блок &b{entity}&c." + permission-inventory-open="&cУ вас нет разрешения от игрока &6{player}&c на открытие &d{block}&c." + permission-item-drop="&cУ вас нет разрешения от игрока &6{player}&c на выбрасывание предмета &d{item}&c." + permission-item-use="&cУ вас нет разрешения на использование предмета &d{item}&c." + permission-option-defaults="&cУ вас нет разрешения на изменение стандартных значений опций." + permission-option-overrides="&cУ вас нет разрешения на управление переопределением опций." + permission-option-use="&cУ вас нет разрешения на использование этой опции." + permission-override-deny="&cДействие, которое вы пытаетесь совершить, было отменено флагом, переопределённым администратором." + permission-player-admin-flags="&cУ вас нет разрешения на изменение флагов для членов администрации." + permission-player-option="&cУ вас нет разрешения на установку опции на игрока." + permission-player-view-others="&cУ вас нет разрешения на просмотр других игроков." + permission-portal-enter="&cВы не можете воспользоваться этим порталом, потому что у вас нет разрешения от игрока &6{player}&c на вход в регион в месте назначения." + permission-portal-exit="&cВы не можете воспользоваться этим порталом, потому что у вас нет разрешения от игрока &6{player}&c на выход из региона." + permission-protected-portal="&cУ вас нет разрешения от игрока &6{player}&c на использование портала в этом регионе." + permission-trust="&cУ вас нет разрешения от игрока &6{player}&c на изменение разрешений в этом регионе." + permission-visual-claims-nearby="&cУ вас нет разрешения на отображение регионов поблизости." + player-accrued-blocks-exceeded="&cУ игрока &6{player}&c есть &6{total}&c блоков и добавление ему ещё &6{amount}&c блоков превысит максимальное возможное количество.\n. Уменьшите добавляемое количество или попросите администратора выдать игроку нужное количество блоков." + player-remaining-blocks-2d="&aВы можете занять ещё &6{block-amount}&a блоков." + player-remaining-blocks-3d="&aВы можете занять ещё &6{chunk-amount}&a чанков. &f({block-amount} блоков)" + playerinfo-ui-abandon-return-ratio="&eДоля возврата при удалении региона&f : &a{ratio}" + playerinfo-ui-block-accrued="&eНакоплено блоков&f : &a{amount}&7(&d{block_amount}&f per hour&7)" + playerinfo-ui-block-bonus="&eБонусных блоков&f : &a{amount}" + playerinfo-ui-block-initial="&eНачальное количество блоков&f : &a{amount}" + playerinfo-ui-block-max-accrued="&eМаксимум накопления блоков&f : &a{amount}" + playerinfo-ui-block-remaining="&eОсталось блоков&f : &a{amount}" + playerinfo-ui-block-total="&eВсего блоков&f : &a{amount}" + playerinfo-ui-chunk-total="&eВсего чанков&f : &a{amount}" + playerinfo-ui-claim-level="&eМин./Макс. уровень региона&f : &a{level}" + playerinfo-ui-claim-size-limit="&eОграничения по размеру региона&f : &a{limit}" + playerinfo-ui-claim-total="&eВсего регионов&f : &a{amount}" + playerinfo-ui-economy-block-available-purchase="&eДоступно блоков региона для покупки&f : &a{amount}" + playerinfo-ui-economy-block-cost="&eЦена покупки блока региона&f : &a{amount} за блок" + playerinfo-ui-economy-block-sell-return="&eЦена продажи блока региона&f : &a{amount} за блок" + playerinfo-ui-last-active="&eВ последний раз активен&f : {date}" + playerinfo-ui-tax-current-rate="&eТекущий размер налога на регионы&f : &a{rate}" + playerinfo-ui-tax-global-claim-rate="&eОбщий размер налога на регионы&f : &a{rate}" + playerinfo-ui-tax-global-town-rate="&eОбщий размер налога на города&f : &a{rate}" + playerinfo-ui-tax-total="&eОбщий размер налогов&f : &a{amount}" + playerinfo-ui-title="&bИнформация об игроке" + playerinfo-ui-uuid="&eUUID&f : &7{id}" + playerinfo-ui-world="&eМир&f : &7{name}" + plugin-command-not-found="&cКоманда '&a{command}&c' для плагина &b{id}&c не найдена." + plugin-event-cancel="&cПлагин отменил это действие." + plugin-not-found="&cНе удалось найти плагин с идентификатором &b{id}&c." + plugin-reload="&aGriefDefender перезагружен." + pvp-claim-not-allowed="&aВ этом регионе сражения между игроками запрещены." + pvp-source-not-allowed="&aУ вас нет разрешения на сражение с другим игроком." + pvp-target-not-allowed="&aВы не можете атаковать игроков, которые не могут участвовать в сражениях." + registry-block-not-found="&cБлок {id}&c не найден в реестре." + registry-entity-not-found="&cСущность {id}&c не найдена в реестре." + registry-item-not-found="&cПредмет {id}&c не найден в реестре." + resize-overlap="&cНельзя изменить размер региона - в этом случае он будет пересекаться с другим регионом." + resize-overlap-subdivision="&cНельзя изменить размер суб-региона - в этом случае он будет пересекаться с другим суб-регионом. Воспользуйтесь &f/abandon&c, чтобы удалить его, или используйте лопату на его угол, чтобы изменить его размер." + resize-same-location="&cУгол региона уже находится здесь. Нужно выбрать другое место для угла региона, чтобы изменить его размер." + resize-start="&aИзменение размера региона. Используйте лопату на новой точке размещения этого угла." + resize-success-2d="&aРазмер региона изменён. У вас осталось ещё &6{block-amount}&a блоков." + resize-success-3d="&aРазмер региона изменён. У вас осталось ещё &6{chunk-amount}&a чанков. &f({block-amount} блоков)" + result-type-change-deny="&cВы не можете изменить вид региона на {type}." + result-type-change-not-admin="&cВы не имеете администраторских привилегий на изменение вида на {type}&c." + result-type-child-same="&cРегионы вида &f{type}&c не могут иметь прямые суб-регионы вида &f{type}&c." + result-type-create-deny="&cРегионы вида &f{type}&c не могут быть созданы в &f{target_type}&с." + result-type-no-children="&cРегионы вида &f{type}&c не могут содержать суб-регионы." + result-type-only-subdivision="&cРегионы вида &f{type}&c могут содержать только суб-регионы." + result-type-requires-owner="&cНе удалось изменить регион вида {type} на {target_type}. Требуется владелец." + schematic-abandon-all-restore-warning="&6Вы уверены, что хотите &nудалить&6 &cВСЕ&6 ваши регионы? &cВСЕ ДАННЫЕ БУДУТ УДАЛЕНЫ&f!!&6 При подтверждении все регионы будет восстановлены в состояние, в котором они были при создании." + schematic-abandon-restore-warning="&6Вы уверены, что хотите &nудалить&6 этот регион? &cВСЕ ДАННЫЕ БУДУТ УДАЛЕНЫ&f!!&6 При подтверждении этот регион будет восстановлен в состояние, в котором они были при создании." + schematic-create="&aСоздание резервной копии..." + schematic-create-complete="&aРезервная копия создана." + schematic-create-fail="&cНе удалось создать резервную копию." + schematic-deleted="&aРезервная копия {name} удалена." + schematic-none="&aДля этого региона нет резервных копий." + schematic-restore-click="&aНажмите, чтобы восстановить регион из резервной копии.\nИмя: {name}\nСоздана: {date}" + schematic-restore-confirmation="&6Вы уверены, что хотите восстановить регион? Нажатие Подтвердить восстановит &cВСЕ&6 данные региона из резервной копии. Используйте аккуратно!" + schematic-restore-confirmed="&aРегион успешно восстановлен из резервной копии &b{name}&a." + spawn-not-set="&cТочка возрождения в регионе не задана." + spawn-set-success="&aТочка возрождения в регионе успешно установлена в &b{location}&a." + spawn-teleport="&aВы телепортированы в точку возрождения в регионе в &b{location}&a." + tax-claim-expired="&cЭтот регион заморожен за неуплату налогов. Текущая задолженность: '&a{amount}&c'.\nУ вас осталось '&a{days}&c' дней на то, чтобы зачислить оплату на счёт банка региона, чтобы разморозить его.\nПри неуплате налога регион будет удалён.\nПримечание: чтобы зачислить средства на счёт банка региона, воспользуйтесь &f/claimbank&c deposit <amount>." + tax-claim-paid-balance="&aЗадолженность по налогам в размере '&6{amount}&a' оплачена. Ваш регион разморожен и снова может быть использован." + tax-claim-paid-partial="&aЗадолженность по налогам в размере '&6{amount}&a' частично оплачена. Чтобы разморозить регион, остаток долга в размере '&6{balance}&a' должен быть уплачен." + tax-info="&aСледующая выплата по налогам в размере &6{amount}&a будет списана с вашего счёта &b{date}&a." + tax-past-due="&cУ вас есть задолженность по налогам в размере &a{balance}&c, которая должна быть выплачена до &b{date}&c. В случае неуплаты ваша собственность будет потеряна." + teleport-confirm="&aВы уверены, что хотите телепортироваться на {pos}? Нажмите Подтвердить, чтобы продолжить." + teleport-delay-notice="&aВы будете телепортированы через {delay}&a секунд." + teleport-move-cancel="&cТелепортация отменена! Вы не можете двигаться, пока идёт отсчёт." + teleport-no-safe-location="&cНе удалось найти безопасную точку для телепортации в этот регион!\n&aВоспользуйтесь '&f/claiminfo&a', чтобы установить безопасную точку телепортации/возрождения." + teleport-success="&aВы были телепортированы в регион {name}&a." + title-accessor=ДОСТУП + title-all=ВСЕ + title-builder=СТРОИТЕЛЬСТВО + title-claim=РЕГИОН + title-container=КОНТЕЙНЕРЫ + title-default=СТАНДАРТ + title-inherit=НАСЛЕДОВАНИЕ + title-manager=УПРАВЛЕНИЕ + title-override=ПЕРЕОПРЕДЕЛЕНИЕ + title-own=ВЛАДЕЛЕЦ + tool-not-equipped="&cУ вас нет &f{tool}&c." + town-chat-disabled="&aЧат города отключён." + town-chat-enabled="&aЧат города включён." + town-create-not-enough-funds="&cУ вас недостаточно средств для создания города за &a{amount}&c. На вашем счёте сейчас &a{balance}&c и для создания нужно ещё &a{amount-needed}&c." + town-name="&aИмя города установлено: &f{name}&a." + town-not-found="&cГород не найден." + town-not-in="&cВы не находитесь в городе." + town-owner="&cЭто принадлежит городу." + town-tag="&aТег города установлен: &f{tag}&a." + town-tag-clear="&aТег города удалён." + town-tax-no-claims="&cЧтобы попасть под налогообложение этим городом, нужно иметь в нём собственность." + trust-already-has="&cУ пользователя &f{target}&c уже есть разрешение вида &f{type}&c." + trust-click-show-list="Нажмите здесь, чтобы вывести список всех игроков и групп, вписанных в регион." + trust-grant="&aИгроку &6{target}&a выдано разрешение вида &6{type}&a в текущем регионе." + trust-individual-all-claims="&aИгроку &6{player}&a выданы полные права во всех ваших регионах. Чтобы отменить разрешения во ВСЕХ ваших регионах, воспользуйтесь &f/untrustall&a." + trust-invalid="&cНеверный вид разрешения.\nДоступные виды: accessor, builder, container и manager." + trust-list-header="Разрешения в этом регионе:" + trust-no-claims="&cУ вас нет регионов, в которые можно было бы вписать игрока." + trust-plugin-cancel="&cНе удалось выдать разрешения игроку &f{target}&c. Плагин не разрешил." + trust-self="&cНикому нельзя доверять. Особенно себе." + tutorial-claim-basic="&eНажмите для получения по созданию регионов: &ahttp://bit.ly/mcgpuser" + ui-click-confirm="Нажмите для подтверждения." + ui-click-filter-type="&7Нажмите здесь, чтобы вывести только &f{type}&7." + untrust-individual-all-claims="&aОтозваны все разрешения игрока &6{target}&a во ВСЕХ ваших регионах. Чтобы настраивать разрешения в одном регионе, войдите в него и используйте &f/untrust&a." + untrust-individual-single-claim="&aОтозваны все разрешения игрока &6{target}&a в этом регионе. Чтобы настроаивать разрешения во ВСЕХ ваших регионах, воспользуйтесь &f/untrustall&a." + untrust-no-claims="&cУ вас нет регионов, из которых можно было бы выписать игрока." + untrust-owner="&6{owner}&a является владельцем региона и не может быть выписан из него." + untrust-self="&cНельзя не доверять себе." + } +} diff --git a/sponge/src/main/resources/com/griefdefender/internal/pagination/font-sizes.json b/sponge/src/main/resources/com/griefdefender/internal/pagination/font-sizes.json new file mode 100644 index 0000000..b6954cb --- /dev/null +++ b/sponge/src/main/resources/com/griefdefender/internal/pagination/font-sizes.json @@ -0,0 +1,50 @@ +# To create/update this file: Use the following code (accurate as of MC 1.8), run on the client. The nonUnicode string constant is extracted from FontRenderer -- it's inlined though. +# This must be run while a game is active -- I've stuck this in a command +# ```java +# public void printCharSizes() { +# ConfigurationNode node = SimpleConfigurationNode.root(); +# String nonUnicode = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153" +# + "\u015e" +# + "\u015f\u0174" +# + "\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;" +# + "<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; +# List<Integer> nonUnicodeCodePoints = new ArrayList<Integer>(nonUnicode.length()); +# for (int i = 0; i < nonUnicode.length(); ++i) { +# nonUnicodeCodePoints.add(nonUnicode.codePointAt(i)); +# } +# node.getNode("non-unicode").setValue(nonUnicode); +# int[] charWidths = Minecraft.getMinecraft().getRenderManager().getFontRenderer().charWidth; +# final List<Integer> charWidthsList = new ArrayList<Integer>(charWidths.length); +# for (int i : charWidths) { +# charWidthsList.add(i); +# } +# charWidthsList.set(32, 4); // Space is handled weirdly +# node.getNode("char-widths").setValue(charWidthsList); +# InputStream var1 = null; +# +# final List<Byte> glyphSizezList = new ArrayList<Byte>(65536); +# try { +# var1 = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream(); +# int b; +# while ((b = var1.read()) != -1) { +# glyphSizezList.add((byte) b); +# } +# } catch (IOException var6) { +# throw new RuntimeException(var6); +# } finally { +# IOUtils.closeQuietly(var1); +# } +# node.getNode("glyph-widths").setValue(glyphSizezList); +# +# try { +# HoconConfigurationLoader.builder() +# .setRenderOptions(ConfigRenderOptions.concise()) +# .setFile(new File("font-sizes.conf")) +# .build().save(node); +# } catch (IOException e) { +# logger.error("Unable to write character size information", e); +# } +# } +# ``` + +{"char-widths":[6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,4,4,6,7,6,6,6,6,6,6,1,1,1,1,1,1,1,4,2,5,6,6,6,6,3,5,5,5,6,2,6,2,6,6,6,6,6,6,6,6,6,6,6,2,2,5,6,5,6,7,6,6,6,6,6,6,6,6,4,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,4,6,6,3,6,6,6,6,6,5,6,6,2,6,5,3,6,6,6,6,6,6,6,4,6,6,6,6,6,6,5,2,5,7,6,6,6,6,6,6,6,6,6,6,6,6,4,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,4,6,6,3,6,6,6,6,6,6,6,7,6,6,6,2,6,6,8,9,9,6,6,6,8,8,6,8,8,8,8,8,6,6,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,6,9,9,9,5,9,9,8,7,7,8,7,8,8,8,7,8,8,7,9,9,6,7,7,7,7,7,9,6,7,8,7,6,6,9,7,6,7,1],"glyph-widths":[15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,68,38,22,23,23,23,68,53,36,23,23,52,22,52,22,22,38,22,22,22,22,22,22,22,22,52,52,38,22,21,22,22,22,22,22,22,22,22,22,22,38,23,22,22,22,22,22,22,23,22,22,23,22,23,22,22,23,22,70,22,19,22,23,36,22,22,22,22,22,21,22,22,38,21,22,38,23,22,22,22,22,22,22,21,22,22,23,22,22,22,53,68,36,23,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,68,23,23,22,23,68,22,37,7,38,22,22,15,7,22,36,23,38,38,53,38,22,52,36,36,38,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,38,38,38,38,6,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,23,22,22,22,22,22,38,38,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,38,22,22,22,22,22,23,23,22,22,22,22,22,22,22,22,22,22,6,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,7,6,22,22,38,38,22,22,38,38,38,38,22,38,23,22,6,6,22,22,38,22,38,22,38,22,21,6,38,22,22,6,6,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,6,6,22,22,22,22,22,22,22,22,22,22,23,21,23,21,23,21,22,22,22,22,22,22,22,22,23,23,22,23,22,23,23,22,23,22,22,22,22,22,22,37,6,6,22,22,22,22,22,23,23,6,6,22,22,22,22,22,22,23,21,23,22,22,21,38,22,22,38,22,23,6,22,22,22,22,22,22,23,23,23,22,22,22,21,21,23,21,23,23,23,22,22,23,23,22,22,22,22,22,22,22,22,22,21,22,51,36,21,68,23,23,23,23,23,7,23,23,23,22,22,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,22,22,22,22,22,22,22,22,23,23,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,6,6,22,22,22,22,23,21,22,22,22,22,22,23,22,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,37,23,22,21,6,6,6,6,6,22,23,23,22,22,22,22,6,22,22,6,23,23,23,23,22,22,23,23,22,22,22,22,22,22,23,23,22,22,23,22,22,23,22,23,23,22,22,22,22,22,22,22,38,53,38,22,22,54,23,23,23,23,23,23,22,22,23,23,23,22,22,23,22,22,22,22,22,22,22,21,21,21,22,21,22,23,22,22,22,23,22,23,23,23,22,22,22,22,22,22,22,22,22,23,38,22,22,22,23,22,22,23,23,23,23,23,23,23,39,22,23,22,21,23,38,38,37,38,21,23,21,21,21,36,22,52,52,52,37,37,22,22,37,37,23,23,37,37,68,37,37,37,68,37,37,37,36,36,37,37,21,21,21,21,22,52,37,70,22,23,6,22,21,36,21,21,21,21,21,21,21,21,21,20,21,22,22,21,21,53,53,37,36,22,22,22,52,36,36,36,36,22,22,22,22,22,22,22,22,7,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,7,7,22,22,7,22,7,22,22,22,6,22,22,7,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,15,22,22,22,22,22,22,7,22,22,22,22,22,7,7,7,7,7,7,7,22,22,22,22,22,22,22,22,6,22,22,22,22,21,22,6,21,37,37,22,6,0,0,52,22,22,22,52,0,0,0,0,0,51,21,22,53,22,22,22,0,22,0,23,23,21,22,22,22,23,22,22,22,22,38,22,23,22,22,22,22,23,22,0,22,23,23,23,22,23,23,38,23,22,22,22,53,22,22,22,23,22,22,38,22,22,53,38,22,22,22,22,22,22,22,22,23,22,22,23,22,23,23,37,22,22,22,23,22,22,23,23,23,23,23,23,23,22,22,22,22,22,38,22,21,22,21,7,23,22,38,22,22,22,22,7,22,22,22,23,23,22,22,22,38,22,21,21,22,22,22,22,23,22,22,22,22,22,22,23,22,22,22,38,38,22,7,23,23,22,22,23,23,22,22,22,22,7,22,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,22,23,22,23,7,23,22,22,22,22,22,22,22,22,22,23,22,23,22,22,22,22,22,22,22,22,22,22,22,23,22,23,22,23,22,23,7,23,22,22,22,22,22,22,22,22,22,22,22,38,38,21,7,23,22,22,22,22,38,23,23,23,22,23,23,23,23,23,23,23,23,23,23,22,22,23,23,22,22,23,22,23,23,23,23,23,23,23,23,23,23,22,22,23,22,6,22,22,7,13,30,23,23,22,22,22,22,22,22,23,23,22,22,23,23,22,22,22,22,23,23,23,23,7,7,23,23,23,23,23,23,22,22,22,22,23,23,23,38,23,38,23,23,7,7,23,23,22,22,22,22,22,22,22,22,38,23,23,22,22,23,23,22,22,23,23,22,22,23,23,53,22,22,22,22,23,23,22,22,22,22,22,22,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,23,22,23,23,22,22,22,22,22,22,22,21,22,22,22,22,22,22,23,23,23,23,23,23,7,7,23,23,22,22,23,23,22,22,22,22,7,7,7,7,7,7,23,22,22,23,22,22,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,23,22,23,23,22,22,22,22,23,23,22,22,23,22,22,22,22,23,22,23,22,23,22,22,22,23,22,23,22,23,22,22,22,23,21,22,22,23,0,0,35,35,36,37,36,21,21,0,23,22,23,23,22,23,21,22,23,23,22,21,23,23,22,22,22,23,23,23,21,22,23,22,39,23,23,23,22,23,23,22,22,54,23,23,22,23,23,0,52,22,0,0,0,0,0,0,22,22,22,22,22,22,22,22,6,22,22,22,23,23,7,23,6,22,22,22,22,22,22,22,22,22,22,22,23,6,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,68,22,22,53,22,22,37,22,0,0,0,0,0,0,0,0,22,23,22,22,22,36,38,22,22,36,22,22,38,6,23,36,37,22,6,22,22,38,22,22,22,23,22,0,0,0,0,0,23,23,23,70,22,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,0,0,28,27,45,21,23,60,52,21,62,40,23,76,60,44,60,74,58,90,90,90,90,52,0,0,121,23,0,37,23,36,22,53,22,68,22,38,22,22,22,22,22,22,22,22,22,23,23,23,23,22,22,22,22,6,6,22,22,22,7,22,22,22,22,38,22,26,22,22,22,22,22,22,22,22,22,22,22,23,22,22,90,90,90,90,90,90,90,90,90,0,36,37,38,23,22,22,23,23,23,23,21,52,52,23,22,24,22,38,22,22,36,38,23,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,23,22,22,22,22,23,23,23,23,23,22,22,22,22,22,22,22,22,22,22,6,30,6,22,22,22,6,6,6,6,6,6,23,22,23,22,22,22,22,22,22,22,22,37,22,22,22,22,22,22,22,22,22,22,22,22,6,22,22,22,22,23,23,37,37,61,60,90,74,58,74,76,15,31,90,90,90,90,76,90,38,23,58,90,31,90,90,90,90,22,23,36,37,38,23,22,23,22,23,23,23,23,23,22,37,22,26,59,52,52,52,52,22,22,22,22,22,22,30,29,30,0,15,15,22,28,30,30,39,39,30,60,70,30,30,30,37,30,63,43,29,46,29,29,30,30,13,13,61,39,30,29,30,30,23,22,22,22,22,22,22,22,22,22,22,22,22,22,59,59,22,22,22,22,22,22,22,22,22,22,126,22,0,0,60,30,30,22,22,22,22,22,22,22,22,22,22,22,23,23,22,22,22,22,22,6,6,6,38,38,22,22,22,22,22,22,23,22,22,23,22,22,36,38,22,23,22,22,22,23,23,23,23,23,22,19,22,6,22,21,37,37,37,37,37,54,22,22,38,37,21,44,54,38,38,22,54,21,7,6,20,19,22,22,44,44,44,22,22,37,37,37,37,22,22,22,22,22,23,22,6,22,22,22,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,38,37,37,36,21,22,22,21,51,22,6,21,21,21,21,22,38,37,37,21,38,20,22,38,22,22,21,22,37,22,21,21,21,38,37,37,21,21,22,22,38,22,22,22,22,22,22,22,22,22,36,36,38,23,36,36,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,75,90,93,47,31,31,15,15,15,15,15,15,15,15,15,15,31,31,31,31,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,63,15,63,15,15,15,15,15,47,15,15,15,15,15,15,15,15,15,15,15,15,0,0,90,127,95,10,95,74,91,90,90,90,74,74,58,95,95,95,95,90,0,0,30,90,90,90,90,0,0,0,15,15,15,15,15,15,15,15,15,15,75,75,120,106,75,90,76,75,45,75,75,61,60,75,106,-119,31,0,0,0,0,0,0,0,0,15,15,75,15,15,0,90,95,94,0,15,15,15,15,15,15,31,30,0,0,30,30,0,0,30,30,15,47,47,15,45,15,15,15,15,30,15,15,15,15,63,15,31,15,79,15,0,31,15,15,15,15,15,15,0,15,0,0,0,31,15,15,15,0,0,90,13,95,12,79,90,91,90,90,0,0,10,10,0,0,15,15,90,75,0,0,0,0,0,0,0,0,95,0,0,0,0,15,15,0,15,31,75,91,94,0,0,60,76,60,45,44,44,45,76,29,45,15,15,59,28,59,45,45,-119,44,61,30,0,0,0,0,0,0,90,90,95,0,15,15,15,14,15,15,0,0,0,0,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,0,15,15,0,15,15,0,15,15,0,0,90,0,79,9,95,74,74,0,0,0,0,92,92,0,0,92,92,94,0,0,0,74,0,0,0,0,0,0,0,15,15,15,15,0,15,0,0,0,0,0,0,0,43,75,75,42,74,45,76,75,43,43,90,90,15,15,46,90,0,0,0,0,0,0,0,0,0,0,0,90,90,95,0,30,15,92,76,75,45,46,45,30,0,30,30,15,0,15,15,73,27,60,93,42,45,42,27,59,60,21,72,39,42,27,75,59,89,75,58,0,43,57,46,62,76,76,57,0,42,43,0,75,60,75,44,42,0,0,90,21,95,10,95,91,91,90,90,94,0,94,94,95,0,95,95,91,0,0,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,45,92,94,0,0,43,75,75,59,60,43,60,43,57,41,0,46,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,95,95,0,27,29,26,26,26,27,42,90,0,0,22,43,0,0,74,44,27,75,75,59,28,75,59,75,59,43,75,57,59,59,59,26,59,26,91,42,0,59,28,42,26,27,27,75,0,27,27,0,42,59,59,43,26,0,0,90,21,92,76,92,75,91,90,74,0,0,10,13,0,0,12,13,90,0,0,0,0,0,0,0,0,93,93,0,0,0,0,59,59,0,29,43,90,93,93,0,0,43,75,75,43,92,60,75,75,74,74,27,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,45,0,43,11,43,26,27,12,0,0,0,45,45,27,0,27,27,13,27,0,0,0,27,26,0,27,0,11,59,0,0,0,15,27,0,0,0,27,29,72,0,0,0,58,59,75,60,27,29,42,27,12,28,28,14,0,0,0,0,95,94,90,95,75,0,0,0,10,10,14,0,15,15,15,90,0,0,15,0,0,0,0,0,0,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,90,27,29,45,29,29,30,45,27,30,42,44,29,27,46,15,75,13,15,28,47,0,0,0,0,0,0,95,95,95,0,44,44,25,27,28,13,12,43,0,60,60,60,0,59,59,43,74,26,72,28,59,28,28,59,44,43,27,57,27,27,44,44,57,57,57,43,0,60,60,28,29,28,28,57,44,27,44,0,60,58,61,60,28,0,0,0,92,95,90,74,95,95,95,95,0,74,74,75,0,43,28,43,58,0,0,0,0,0,0,0,92,75,0,28,59,0,0,0,0,0,0,15,47,76,45,0,0,90,75,44,107,75,93,28,92,29,28,0,0,0,0,0,0,0,0,91,-120,106,74,60,92,92,60,0,0,95,95,0,44,44,25,11,11,14,13,43,0,28,28,28,0,28,28,28,58,45,43,12,28,12,12,26,44,43,27,57,27,27,28,59,57,57,57,43,0,28,28,28,28,12,12,57,44,26,27,0,28,26,29,43,76,0,0,90,22,95,90,95,95,95,95,95,0,77,79,79,0,79,79,62,92,0,0,0,0,0,0,0,95,95,0,0,0,0,0,0,0,44,0,15,31,76,61,0,0,90,75,91,60,75,45,28,92,26,28,0,74,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,95,0,13,15,27,12,57,44,59,44,0,12,12,15,0,43,45,29,28,29,29,30,28,44,45,28,14,29,74,73,45,44,28,45,28,58,44,44,0,28,28,28,58,60,45,44,59,44,43,59,28,45,28,12,12,0,0,0,108,95,95,95,95,95,95,95,0,10,10,10,0,15,15,15,95,0,0,0,0,0,0,0,0,0,95,0,0,0,0,0,0,0,0,28,29,44,44,0,0,90,44,28,29,58,60,30,74,42,27,45,44,30,60,43,44,0,0,0,30,29,44,59,28,44,29,0,0,95,95,0,60,14,31,31,59,59,59,30,15,15,30,15,42,45,15,29,29,15,0,0,0,12,44,10,26,27,30,42,46,44,14,13,14,29,42,27,44,27,14,44,12,27,92,44,13,0,74,61,61,44,29,44,44,42,57,0,44,0,0,27,11,43,26,29,62,44,0,0,0,95,0,0,0,0,95,95,95,76,76,59,0,59,0,95,10,15,10,15,15,15,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,95,95,14,0,0,0,0,0,0,0,0,0,0,0,0,22,21,22,22,22,22,21,22,22,38,22,23,22,22,22,38,22,23,23,22,22,22,22,21,22,38,38,23,23,23,23,22,22,22,21,22,22,22,21,22,23,23,22,23,22,23,21,37,23,21,22,22,22,22,22,22,22,22,0,0,0,0,22,69,55,23,22,22,21,22,22,22,23,23,22,23,22,23,22,22,22,22,22,22,22,22,22,23,22,38,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,0,22,0,0,21,22,0,22,0,0,22,0,0,0,0,0,0,22,22,22,6,0,22,22,22,7,6,6,6,0,22,22,22,0,22,0,22,0,0,22,7,0,22,22,38,37,23,37,6,22,22,22,22,22,22,0,23,22,22,0,0,69,38,22,22,6,0,22,0,22,22,23,22,22,22,0,0,22,22,21,22,22,22,22,22,22,22,0,0,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,12,12,7,22,7,7,22,39,23,52,15,52,38,52,52,22,23,7,37,7,30,31,31,22,37,23,23,53,23,23,39,55,39,39,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,53,23,22,22,23,23,12,46,22,39,23,6,23,23,23,23,23,23,23,23,0,23,23,39,23,23,39,23,39,23,23,39,23,23,23,23,23,23,23,23,23,23,23,39,23,23,39,23,23,39,23,23,23,23,23,23,23,0,0,0,0,22,22,23,22,22,22,23,22,23,6,6,7,7,22,22,22,23,23,23,87,23,22,22,40,23,6,23,0,0,0,0,23,23,23,23,23,23,23,23,0,23,23,22,23,23,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,0,23,23,23,38,22,23,23,44,29,14,44,44,14,14,14,0,44,44,29,22,89,29,75,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,41,41,13,40,41,12,42,9,25,28,8,6,24,26,12,10,10,40,57,25,41,57,57,26,57,10,25,10,40,26,10,6,11,27,44,11,25,25,57,58,14,15,94,95,90,90,90,91,95,90,90,90,90,90,91,95,90,90,94,30,90,90,31,42,57,55,39,73,89,89,72,57,89,68,37,22,92,78,12,57,57,57,57,39,41,95,95,90,91,91,93,75,91,90,90,90,74,94,94,94,90,91,94,94,94,94,94,94,94,29,29,27,91,90,91,90,58,58,58,11,11,11,11,27,27,27,57,28,57,91,95,10,90,90,94,94,94,95,94,95,91,74,94,90,89,73,89,90,74,74,90,90,90,0,0,0,0,123,76,6,6,6,6,21,6,6,6,21,21,6,6,21,6,6,22,6,21,22,6,6,21,22,21,21,21,22,22,22,22,21,6,6,22,22,21,6,6,0,0,0,0,0,0,0,0,0,0,38,38,38,23,38,38,22,23,38,38,23,38,38,23,38,22,23,38,22,22,23,38,23,38,22,38,38,38,39,22,38,38,38,38,38,38,38,22,21,38,38,39,38,38,38,0,0,0,40,40,40,40,40,40,40,40,40,55,25,55,40,41,40,40,40,24,40,45,45,45,45,45,45,45,45,45,45,61,45,45,45,45,30,30,29,30,30,45,45,45,45,76,45,61,61,61,61,61,61,30,46,61,61,61,61,61,61,61,60,60,58,61,42,61,61,62,61,62,29,61,61,61,61,61,55,45,42,44,45,45,45,45,42,43,45,60,45,44,0,0,0,0,0,15,15,-66,-66,-66,-66,-101,-98,-101,-98,46,46,46,43,46,46,43,46,43,46,46,43,-69,44,46,46,46,46,46,43,46,46,46,46,46,46,46,46,45,46,46,46,46,46,46,46,46,46,47,46,46,46,46,47,46,46,46,-98,-98,46,46,46,-117,-117,75,46,59,60,0,0,0,0,0,91,76,77,91,76,77,91,91,76,59,59,60,59,60,60,91,91,60,59,44,106,91,76,91,91,75,91,29,30,45,45,44,30,45,45,45,31,45,45,15,45,30,31,31,15,44,30,45,45,46,45,45,45,61,31,46,46,46,74,45,45,46,91,60,61,44,61,59,61,29,45,45,89,44,29,45,75,29,29,29,29,74,0,0,0,0,0,0,41,45,75,74,74,59,44,30,74,44,44,74,44,58,45,78,60,30,30,60,29,60,60,47,45,14,30,45,30,44,44,29,60,29,30,60,45,60,60,30,58,43,59,44,60,58,60,44,74,44,44,74,60,91,74,77,42,45,45,59,45,59,59,44,60,43,43,60,43,60,43,43,61,0,31,60,60,30,0,0,60,43,43,60,43,44,60,0,44,0,31,60,60,30,0,0,74,44,44,74,61,44,74,77,42,45,45,42,28,28,42,44,28,28,28,28,28,45,45,45,45,45,45,28,28,29,45,45,58,45,45,75,45,75,45,45,29,0,30,61,45,29,0,0,89,91,74,42,90,41,44,42,60,44,44,44,43,26,44,44,74,44,44,74,44,74,74,42,59,29,29,59,29,59,59,44,29,0,30,62,29,29,0,0,27,14,14,27,31,28,27,0,15,0,15,30,31,15,0,0,44,14,44,44,44,14,44,30,75,45,28,59,28,75,75,0,74,44,44,74,61,43,74,77,45,29,29,45,29,29,45,45,74,75,60,91,60,27,44,30,60,45,44,75,44,44,59,78,75,45,44,58,44,60,59,78,43,29,28,43,28,45,43,46,57,44,60,74,60,59,74,74,45,0,45,74,60,45,0,0,75,44,60,75,60,59,74,59,59,29,29,59,29,44,59,62,14,15,15,30,15,13,30,30,75,45,45,75,45,44,75,61,75,45,45,58,44,44,58,61,75,45,11,7,11,75,75,44,60,59,43,43,44,41,43,43,60,60,60,60,60,60,60,60,60,45,44,0,0,0,0,75,60,103,90,90,90,56,89,120,75,59,76,75,75,59,59,59,60,75,75,60,60,60,75,60,75,44,59,75,45,0,0,0,15,15,29,14,44,45,42,30,61,45,60,43,61,44,60,45,52,37,52,52,37,28,22,30,30,22,0,0,0,0,0,0,22,23,23,23,23,38,23,22,22,22,22,23,22,22,22,23,22,22,23,22,22,22,22,22,22,23,22,22,22,23,22,23,22,23,22,22,22,23,23,22,22,23,23,22,23,23,22,22,23,23,22,22,23,23,23,23,23,23,22,23,23,22,22,22,23,23,22,23,23,23,22,23,23,23,23,22,23,22,22,23,22,22,22,23,22,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,23,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,23,23,23,23,22,23,22,21,37,37,23,23,37,37,23,37,36,21,21,21,23,23,22,22,23,23,23,23,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,22,37,51,20,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,23,23,23,23,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,21,21,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,37,37,37,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,22,21,22,21,21,21,21,22,21,21,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,36,22,36,22,36,36,36,44,45,44,44,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,37,21,21,21,23,23,23,23,23,23,21,21,21,21,21,21,23,23,36,37,22,23,23,23,23,23,23,23,23,7,23,21,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,44,23,23,23,23,23,23,23,23,23,23,23,21,22,21,44,44,44,44,44,44,44,23,21,21,21,21,23,23,23,23,31,31,31,31,31,31,31,23,31,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,21,23,23,23,23,23,21,21,21,21,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,22,22,22,23,23,22,22,22,22,23,23,22,22,22,22,23,23,23,6,6,23,22,22,22,22,22,22,22,22,22,22,6,22,22,22,6,6,6,6,21,23,23,22,22,22,22,23,23,22,22,6,22,23,23,22,22,6,22,21,22,22,6,6,6,6,23,23,22,22,6,22,22,22,22,22,22,22,6,6,6,44,44,44,44,44,75,75,46,46,46,29,60,60,21,21,21,21,60,60,6,6,6,23,6,6,6,6,6,6,76,59,7,7,7,7,45,28,12,12,12,46,6,6,60,60,60,60,6,6,6,6,60,60,60,60,44,44,44,44,44,44,44,44,44,44,44,44,21,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,44,23,36,44,31,31,31,31,31,31,31,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,47,13,0,0,0,6,6,5,6,5,6,4,4,4,6,6,4,6,54,20,6,6,6,21,6,6,6,6,6,6,4,6,6,6,36,6,38,6,51,36,6,6,6,4,6,6,6,20,21,51,36,6,6,4,5,21,21,4,6,6,6,6,36,22,6,6,6,6,6,12,6,12,6,6,6,6,51,6,21,6,36,19,6,6,6,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,30,75,30,45,30,30,30,59,30,28,30,30,0,30,76,46,46,93,92,90,0,0,0,0,0,0,0,0,0,0,0,29,29,91,59,59,59,60,44,28,29,41,45,28,60,43,30,30,60,93,92,62,89,60,0,0,0,0,0,0,0,0,0,44,44,75,59,91,61,30,44,92,45,74,60,45,60,43,60,44,74,93,92,0,0,0,0,0,0,0,0,0,0,0,0,30,30,36,44,29,30,13,30,29,30,45,30,30,0,30,30,29,0,93,92,0,0,0,0,0,0,0,0,0,0,0,0,75,90,75,44,74,43,75,46,61,90,91,59,46,45,45,75,75,77,59,90,76,75,76,59,59,61,89,61,90,76,60,60,61,60,44,44,29,91,62,91,75,59,75,76,78,76,77,75,76,91,75,92,15,15,94,92,91,92,92,90,90,90,11,15,15,10,10,10,14,14,90,95,94,90,75,90,92,92,90,94,93,43,15,90,56,24,20,30,15,74,15,90,38,75,0,0,74,59,44,44,77,75,59,46,60,44,0,0,0,0,0,0,20,21,34,7,21,21,36,6,36,21,0,0,0,0,0,0,29,120,74,74,75,14,120,72,60,60,120,15,15,15,15,0,45,45,45,45,45,44,45,45,44,45,0,0,0,0,0,0,60,28,44,60,60,60,60,60,28,61,60,44,60,12,78,78,76,78,75,43,40,24,40,24,56,28,44,44,40,40,44,45,45,40,56,123,60,77,60,28,60,60,24,24,60,28,28,77,75,56,24,24,40,56,40,62,78,40,29,44,24,46,40,75,61,56,24,30,61,62,60,60,77,76,24,45,62,62,78,77,60,44,28,24,46,40,45,60,0,0,0,0,0,0,0,0,60,105,75,44,44,90,14,60,61,45,28,24,40,56,40,40,40,40,61,29,30,46,45,60,56,43,62,60,78,76,45,44,44,77,28,45,62,62,72,24,61,10,62,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,56,9,25,8,27,25,24,24,39,42,24,25,25,24,56,10,8,25,24,24,23,57,40,8,26,38,41,25,0,0,0,75,92,75,94,95,78,79,58,42,95,94,94,0,0,0,0,95,95,90,93,95,95,95,93,95,92,90,74,0,0,0,0,25,0,0,0,20,25,74,75,59,74,44,74,74,58,59,58,21,21,21,6,6,22,6,6,22,37,6,37,6,37,6,7,21,6,22,85,37,37,37,37,37,37,37,37,37,6,0,0,23,21,21,22,21,0,0,0,0,0,0,0,0,0,0,0,75,58,59,90,59,59,58,58,90,75,93,73,74,42,42,90,59,74,59,74,60,60,59,59,75,59,57,59,43,59,75,59,59,73,59,74,73,59,59,74,59,59,0,0,0,0,0,0,95,95,95,95,95,10,10,10,95,95,10,95,95,95,95,95,95,89,58,89,59,59,73,59,95,95,0,0,0,0,0,0,75,76,90,106,75,59,90,44,30,74,0,0,0,0,45,45,76,89,59,59,73,73,89,76,74,89,45,45,30,30,45,45,74,73,59,59,72,74,73,76,74,73,45,45,30,30,45,45,23,60,38,7,60,6,23,43,23,23,23,60,60,60,29,30,29,21,29,29,108,29,29,94,95,10,95,10,0,0,7,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,74,90,91,95,45,14,44,15,75,61,60,14,46,15,58,15,10,15,30,30,29,29,27,60,28,44,15,15,27,27,28,43,15,30,43,60,45,60,43,44,14,28,44,29,43,14,60,15,27,43,14,90,95,58,58,95,95,75,79,28,31,26,10,31,15,58,63,95,30,15,30,60,60,43,15,0,0,0,0,105,31,46,10,72,75,58,14,44,30,15,15,105,91,90,45,89,-49,89,89,89,107,58,75,36,14,53,90,90,90,90,90,74,91,90,90,21,20,5,5,20,20,20,5,35,0,0,0,90,75,94,58,73,91,61,92,57,58,45,29,92,91,61,44,93,62,60,43,106,45,77,77,46,75,61,90,75,91,45,60,79,94,92,91,90,90,10,95,90,90,95,0,0,0,31,31,75,92,91,92,91,30,91,75,60,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,57,75,60,45,108,58,60,77,44,107,75,58,59,76,59,60,60,59,74,75,59,75,60,61,44,90,76,59,60,74,74,59,74,61,59,95,95,95,10,10,13,95,95,90,90,90,90,74,90,42,42,10,10,93,90,0,0,0,-120,105,106,91,60,89,105,75,75,75,75,59,106,90,106,0,0,0,59,59,44,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,6,22,22,22,6,22,22,6,22,6,22,6,6,6,6,22,7,6,6,7,22,22,6,6,6,52,52,52,6,36,21,51,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,22,6,6,22,21,5,21,22,21,21,38,22,38,21,22,22,23,23,23,23,21,38,21,22,22,22,23,22,39,23,23,23,22,22,22,22,6,22,23,23,22,23,22,21,22,21,6,21,21,21,22,22,21,21,38,38,38,21,21,22,21,22,22,23,22,22,22,22,22,6,21,21,22,22,22,22,22,21,20,23,22,22,22,38,38,22,21,22,22,23,22,6,22,23,22,23,22,21,22,22,22,22,23,21,23,22,7,6,23,21,7,23,23,23,23,7,22,6,22,22,22,45,21,21,23,23,23,23,23,21,23,22,38,23,23,22,22,23,22,22,22,22,22,23,23,23,23,22,60,39,22,21,7,22,22,21,21,38,38,21,38,20,21,21,37,21,21,22,21,36,54,23,23,23,23,38,22,21,37,21,22,23,23,38,22,38,38,23,23,22,38,22,22,22,22,22,22,22,6,6,6,22,6,6,7,22,22,23,6,22,6,6,6,7,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,6,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,21,22,22,21,22,21,22,22,22,22,22,6,23,22,22,38,38,22,22,22,22,22,22,22,38,22,38,22,38,22,38,22,23,22,23,22,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,21,23,21,23,21,23,22,22,22,22,22,22,22,22,22,22,22,23,22,23,21,22,23,22,23,22,23,21,23,21,23,21,21,22,22,23,22,22,22,22,22,22,22,22,21,23,22,22,37,21,21,22,22,21,22,22,22,22,22,22,22,22,23,39,23,38,22,21,22,21,22,6,22,6,22,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,38,38,38,38,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,22,23,22,23,22,23,22,6,7,22,21,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,6,6,22,22,22,22,22,22,0,0,6,6,6,6,6,6,0,0,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,53,53,21,21,21,21,22,22,6,6,6,6,6,6,6,6,22,22,22,22,22,22,0,0,6,6,6,6,6,6,0,0,22,22,22,22,22,22,22,22,0,7,0,7,0,7,0,7,23,23,23,23,23,23,23,23,7,7,7,7,7,7,23,23,22,22,22,22,22,22,53,53,22,22,22,22,23,23,0,0,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,22,22,22,22,22,22,22,22,6,6,6,6,6,6,6,6,23,23,23,23,23,23,23,23,7,7,7,7,7,7,7,7,22,22,22,22,22,0,22,22,22,22,6,6,22,52,53,52,22,22,22,22,22,0,22,22,6,6,6,6,22,21,21,22,21,37,22,22,0,0,22,22,38,38,6,6,0,21,21,22,22,22,22,22,22,22,22,22,23,23,7,7,6,22,22,52,0,0,23,23,23,0,23,23,6,6,7,7,23,52,52,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,37,37,22,22,23,7,53,7,52,52,52,52,22,22,22,22,23,23,21,37,52,38,23,52,15,15,15,15,15,15,15,15,7,7,37,7,7,37,7,7,21,70,19,23,37,22,22,22,22,22,23,37,22,70,19,22,22,22,21,6,38,21,23,52,22,23,22,7,22,6,37,15,21,21,51,6,21,51,51,15,15,15,15,15,15,0,0,0,0,0,15,15,15,15,15,15,38,68,0,0,38,38,38,38,38,38,38,38,38,69,52,38,38,36,38,38,38,38,38,38,38,38,38,38,38,69,52,0,21,21,21,22,21,0,0,0,0,0,0,0,0,0,0,0,22,22,22,7,23,23,23,23,23,23,22,22,22,6,23,23,39,7,22,7,7,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,6,6,22,22,22,22,7,7,7,22,23,14,30,14,14,7,30,30,46,22,22,46,22,7,61,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,6,22,23,23,22,22,6,23,7,7,23,22,6,38,23,7,22,7,23,7,7,23,22,23,23,7,23,22,22,7,7,7,7,22,22,23,23,22,22,21,22,23,22,22,21,22,22,22,7,23,22,23,22,23,22,44,30,61,61,23,44,60,22,22,22,6,60,60,6,22,6,22,23,29,7,21,29,0,0,0,22,22,22,22,22,6,22,22,22,22,22,22,22,53,22,23,23,23,7,7,7,23,23,23,23,22,22,22,23,53,22,23,23,23,23,23,7,23,23,23,23,53,22,23,23,22,22,59,22,22,6,6,7,29,0,0,0,0,0,0,0,22,38,22,38,7,38,22,22,22,22,23,23,23,23,7,38,7,38,23,23,22,38,22,38,38,23,23,23,23,7,7,22,21,38,21,38,23,21,7,7,22,22,22,22,22,22,70,36,22,22,70,36,22,7,22,22,7,22,7,22,22,6,7,23,22,38,22,38,7,38,22,22,22,22,6,23,7,7,38,38,23,38,23,38,22,22,7,23,7,23,23,23,23,23,23,23,23,23,23,38,29,7,7,7,7,44,28,45,29,7,7,45,23,37,22,22,22,23,23,23,22,22,22,22,22,22,22,23,23,22,22,23,23,22,22,38,37,37,23,23,23,22,23,22,22,22,22,52,22,37,22,23,23,22,22,38,23,7,38,23,7,39,39,22,22,22,52,22,22,22,22,22,22,22,23,23,52,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,7,7,37,22,38,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,22,22,23,23,37,37,22,22,23,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,38,38,38,38,22,22,23,23,22,22,38,52,38,38,38,38,38,38,38,22,23,23,22,22,23,23,38,22,38,21,23,23,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,52,7,7,7,27,59,22,59,59,22,25,25,44,42,22,42,22,76,23,6,22,38,38,38,38,53,54,20,54,20,71,20,71,20,22,23,23,23,23,23,23,7,23,22,7,22,20,54,20,54,70,36,22,22,23,23,7,23,22,20,54,7,23,7,23,22,23,7,23,23,22,22,23,38,23,23,23,23,23,23,23,7,7,23,23,23,23,23,23,23,23,23,23,38,23,23,23,23,23,38,38,23,23,23,23,23,38,38,23,38,23,23,23,23,23,23,38,38,38,23,23,23,22,22,37,23,23,22,38,23,23,22,22,54,22,23,22,22,54,23,22,30,30,29,60,38,6,29,29,29,45,59,30,30,44,12,12,12,30,45,45,59,59,59,59,29,30,23,21,29,29,14,29,38,35,38,21,69,21,38,35,38,21,69,21,54,20,54,52,20,54,20,68,7,22,22,30,30,30,30,30,23,0,119,7,7,7,7,38,38,44,44,44,44,44,44,44,44,44,44,44,39,39,29,30,6,68,6,6,6,14,14,14,30,30,30,6,15,15,15,14,14,30,30,29,76,30,29,29,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,30,29,29,30,30,29,30,26,27,27,27,27,27,27,27,28,29,29,29,29,30,30,29,29,27,30,30,25,25,25,25,25,30,22,22,22,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,38,38,38,38,38,38,22,7,23,23,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,30,30,30,30,30,30,30,30,30,30,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,73,59,59,60,59,59,60,59,59,12,11,13,13,12,13,13,13,13,13,29,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,15,30,30,30,30,30,30,30,30,30,30,15,15,15,15,15,15,15,15,15,15,30,7,7,68,52,7,7,68,52,6,6,68,52,71,71,55,55,4,4,4,4,71,71,55,55,4,4,4,4,71,71,55,55,55,55,55,55,4,4,4,4,4,4,4,4,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,6,6,68,52,7,53,71,55,55,4,5,5,71,55,55,4,5,5,71,55,55,4,5,5,7,7,7,7,7,7,7,7,7,71,4,4,71,7,7,7,3,68,71,68,3,52,71,52,7,52,7,52,15,15,15,15,15,15,15,15,15,13,11,9,7,5,3,1,-113,14,15,15,15,-17,7,-113,7,15,15,15,15,-113,15,15,23,23,23,23,23,23,23,23,23,23,37,37,23,23,37,37,7,7,22,22,22,22,22,22,37,37,22,22,22,22,22,22,22,22,37,37,22,22,23,23,23,23,22,23,23,23,23,23,23,23,23,23,23,23,20,71,7,7,7,7,20,71,71,20,23,23,22,22,22,22,21,22,22,22,22,23,23,22,22,7,22,22,22,22,7,7,7,7,22,22,22,22,22,37,37,22,23,7,23,15,23,23,23,38,22,7,7,7,21,23,23,23,23,23,23,38,23,45,44,44,29,30,23,23,23,37,23,37,7,37,29,14,29,38,38,22,38,23,7,44,61,7,23,29,29,29,29,29,29,29,29,29,23,7,7,7,23,39,22,38,38,38,23,22,22,23,7,22,23,23,23,23,7,23,23,23,23,22,22,22,23,23,23,23,22,22,23,23,23,23,22,22,23,23,38,23,23,23,38,23,7,20,22,7,7,22,22,22,23,23,15,14,14,14,14,14,14,14,14,15,14,14,12,45,59,59,59,59,59,59,12,12,12,12,29,29,29,29,29,29,22,22,15,30,29,59,14,44,44,29,14,29,14,14,0,0,29,22,44,44,60,75,44,14,6,13,6,6,38,44,61,29,47,44,6,5,6,6,29,22,22,7,6,6,23,0,0,0,45,45,30,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,13,14,14,0,14,14,14,30,0,0,14,31,28,14,28,14,14,29,14,44,14,28,28,14,14,14,14,59,29,29,14,14,14,14,14,15,14,14,0,14,14,14,14,14,14,14,15,14,14,14,14,14,29,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,30,14,14,14,14,0,46,0,62,62,62,62,0,0,0,29,0,120,105,75,74,74,14,14,0,0,30,59,59,14,29,14,30,54,37,38,38,38,38,39,22,23,6,53,36,22,22,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,44,14,44,14,14,14,14,14,14,14,30,30,30,14,14,91,14,30,30,14,14,14,14,15,0,15,30,14,44,30,44,44,14,44,14,14,14,14,14,0,22,44,21,60,60,38,38,6,30,30,36,0,45,0,0,0,76,6,59,22,22,45,45,30,59,59,29,29,30,30,30,121,6,29,14,31,14,31,38,21,37,37,22,22,54,20,70,19,44,44,45,45,14,14,14,13,14,14,13,13,30,13,30,14,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,38,14,14,31,14,46,30,31,30,89,89,59,59,31,14,31,14,13,14,23,23,30,30,30,30,30,30,30,30,30,31,31,30,30,46,46,45,28,28,28,30,46,30,29,45,45,46,46,46,46,30,30,30,29,29,60,60,37,37,45,45,45,45,45,45,44,44,30,30,30,14,14,14,14,21,30,30,21,21,30,36,30,36,31,14,21,21,31,14,21,21,46,29,21,21,46,29,21,21,30,76,30,76,30,30,30,30,30,30,30,30,76,76,30,14,14,31,14,14,76,30,76,76,30,76,23,23,59,59,38,105,37,22,22,37,37,37,37,37,37,70,19,54,20,54,20,38,21,61,61,61,61,37,37,119,104,46,45,45,46,46,61,61,46,46,46,46,22,22,46,46,46,46,45,45,45,45,59,59,59,59,59,44,59,59,59,59,59,59,61,59,61,61,61,61,29,45,27,27,27,27,27,45,29,29,31,14,59,44,44,6,6,6,6,6,6,6,36,36,91,91,44,59,44,30,45,46,44,30,30,30,30,59,29,29,59,6,91,91,57,57,57,57,57,57,30,21,21,21,23,6,6,6,6,6,29,29,61,61,61,58,59,59,59,30,30,44,59,59,13,74,74,74,74,61,74,74,75,74,74,59,74,74,74,74,74,44,22,37,60,37,21,21,5,21,22,74,21,21,21,21,21,74,74,38,21,21,21,59,75,75,44,14,8,29,29,29,22,22,53,60,75,75,75,75,59,59,37,37,37,37,6,6,75,75,59,59,46,59,59,44,44,45,45,60,77,29,44,44,44,44,59,59,59,59,59,59,44,44,23,23,44,44,5,5,5,5,44,5,5,5,5,5,44,29,30,5,45,59,59,43,60,74,74,74,74,74,74,74,74,75,58,74,74,75,58,5,5,75,58,5,5,21,21,22,22,74,74,74,74,74,74,74,74,75,75,75,75,59,59,45,93,29,59,59,59,59,59,59,59,59,44,59,76,59,59,59,59,59,59,59,59,59,59,30,30,44,44,22,22,22,22,22,22,22,22,44,44,22,22,22,22,30,30,59,59,59,59,22,22,22,22,29,29,59,59,59,59,59,37,59,59,44,43,45,60,45,45,59,59,59,59,59,60,60,21,21,21,21,6,75,74,44,51,44,44,74,74,44,74,75,36,36,44,44,44,44,30,29,6,6,44,44,44,44,30,6,30,30,30,30,60,60,60,60,60,60,60,60,44,30,30,37,37,45,45,44,44,30,30,6,6,6,6,21,21,21,14,14,22,22,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,14,14,30,30,30,30,30,30,0,0,0,29,44,44,61,61,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,6,6,6,6,21,6,6,7,6,6,6,7,37,6,61,6,38,6,22,6,6,7,6,6,6,6,6,7,6,6,60,6,6,6,22,6,6,6,45,28,13,7,6,53,6,22,0,21,38,6,6,6,21,6,23,7,6,6,6,6,37,22,44,22,38,22,21,6,6,6,6,6,6,22,38,6,6,21,60,22,6,6,21,22,22,6,45,45,30,6,22,53,22,22,0,22,21,23,22,22,7,7,22,22,23,23,22,22,22,22,22,0,7,6,23,6,21,21,23,6,22,6,38,53,21,0,0,7,7,22,21,22,21,7,6,6,6,22,22,6,6,22,22,7,7,36,36,22,22,7,6,6,21,6,21,7,6,6,6,7,22,22,22,22,22,6,6,7,7,6,6,23,23,7,7,6,6,6,21,22,21,6,22,21,37,6,22,7,7,7,7,6,6,6,6,7,5,37,37,7,7,22,22,6,6,76,75,22,21,22,22,37,37,6,22,7,22,7,23,6,6,22,22,7,7,6,6,22,7,7,14,23,23,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,21,21,6,37,22,37,22,6,6,21,6,43,22,60,22,37,60,38,21,44,7,7,59,21,5,60,60,6,6,22,22,37,5,59,60,21,22,7,44,21,21,21,22,59,0,0,0,0,0,0,0,0,0,0,37,6,6,6,6,6,6,21,21,22,22,22,6,6,21,6,6,6,52,7,21,6,22,6,60,36,21,6,6,37,22,51,6,51,37,37,6,6,21,52,7,6,6,23,21,6,23,22,21,21,36,6,37,6,0,0,0,0,0,0,0,0,0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,46,29,9,10,9,43,43,61,61,43,8,44,44,44,45,30,28,44,29,13,27,30,0,0,0,0,0,0,0,0,0,44,29,29,44,29,26,45,0,60,60,60,60,60,44,28,0,30,30,30,30,30,30,30,0,15,15,15,14,15,14,14,0,60,60,60,43,60,44,60,0,76,60,60,76,60,60,76,0,11,14,14,11,14,11,11,0,42,27,27,25,27,41,25,0,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,22,22,22,22,22,22,22,22,22,6,37,37,37,37,37,37,21,21,5,5,5,21,21,21,29,30,30,31,18,44,44,44,37,22,22,7,22,23,21,21,23,23,37,37,37,37,37,37,22,22,22,5,22,22,22,7,22,52,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,59,13,44,59,14,73,27,14,43,-99,91,45,59,44,14,30,14,30,45,28,14,28,5,29,5,14,0,13,44,59,30,14,45,4,13,13,29,30,22,14,5,14,5,44,31,5,14,7,5,44,44,29,14,45,14,14,14,14,14,14,28,29,14,14,14,13,5,29,14,14,12,28,5,44,14,14,14,14,14,-83,6,14,14,14,29,60,21,14,14,30,29,14,14,14,6,7,6,14,13,14,14,14,29,46,14,14,46,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,15,119,90,25,14,87,15,14,30,30,14,14,45,14,5,30,29,28,29,29,60,29,45,14,108,90,14,30,14,44,29,14,14,14,14,59,14,14,14,30,14,14,14,13,44,29,29,14,46,44,14,29,14,14,30,13,44,28,29,57,14,29,13,14,30,14,14,14,14,14,14,59,46,27,30,14,14,14,30,14,46,14,45,30,14,29,14,14,14,28,14,30,30,14,30,14,14,14,14,14,13,44,14,14,14,44,14,14,60,14,14,14,14,45,14,30,14,14,14,29,30,29,30,29,14,30,30,14,30,45,46,60,14,45,14,14,14,46,29,30,14,29,14,14,14,14,46,29,30,14,14,14,29,44,14,14,14,30,14,14,14,14,46,14,14,14,14,14,45,14,30,14,14,30,14,30,14,14,14,14,30,14,14,13,14,14,29,30,14,14,29,45,29,30,29,30,46,14,14,14,14,14,14,14,30,29,14,46,14,14,14,14,14,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,44,44,44,45,44,29,44,44,58,29,13,74,89,29,29,57,12,74,43,29,44,60,29,29,29,29,60,59,59,44,60,29,44,29,119,27,44,29,29,30,29,0,0,0,45,29,15,45,14,15,29,30,44,14,30,29,14,30,15,15,44,59,45,30,45,14,59,28,14,45,45,44,29,29,-97,124,-97,124,107,109,107,109,31,31,29,28,31,31,28,28,28,31,31,28,-101,15,14,15,14,15,15,30,15,14,14,29,30,31,45,15,14,15,15,14,14,44,44,14,30,14,30,15,44,30,44,14,14,29,15,29,31,28,28,28,12,28,104,92,0,-120,106,41,41,43,42,41,41,41,41,41,42,42,42,44,41,44,44,74,45,29,29,29,29,29,60,30,30,29,30,29,60,29,29,29,55,39,23,22,23,0,0,0,0,0,0,0,0,41,87,59,14,75,43,43,43,30,44,41,45,106,30,43,77,30,119,90,73,89,60,29,61,43,90,104,90,60,46,44,76,30,45,43,29,0,0,0,0,0,0,0,0,0,0,0,0,22,23,6,37,6,59,22,6,75,6,60,6,5,77,5,59,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,27,27,44,59,74,74,75,44,74,74,59,59,59,75,59,28,28,28,44,44,59,91,59,59,91,44,59,59,76,74,59,59,59,75,75,74,59,74,74,59,59,59,74,59,74,74,90,74,74,59,75,75,43,29,29,44,58,58,59,59,59,59,59,59,59,75,75,75,75,59,74,74,75,90,58,75,75,59,59,59,44,44,43,59,59,59,59,59,44,59,28,45,60,45,30,30,59,74,74,59,59,74,74,59,43,60,45,45,43,60,60,58,58,59,59,59,74,75,75,75,60,59,59,74,45,59,59,59,44,59,59,59,75,58,58,59,29,29,75,59,59,30,44,44,74,59,59,59,59,59,75,44,45,45,45,60,60,44,60,44,44,74,74,74,74,59,59,59,75,75,75,59,59,59,59,59,60,44,44,60,60,59,59,59,59,59,59,45,59,76,76,75,45,45,60,42,59,60,59,59,75,59,59,75,59,91,91,75,29,60,60,60,58,75,59,59,44,44,59,44,44,59,43,43,43,59,44,44,59,43,59,59,44,59,59,59,44,60,60,60,45,59,59,45,43,43,59,58,58,29,59,59,45,59,59,45,59,59,59,45,45,30,60,60,60,29,74,74,74,44,44,44,59,59,60,75,44,44,59,60,60,44,43,60,60,43,43,75,75,59,59,75,29,75,75,29,74,44,44,29,60,60,59,59,59,59,60,44,44,60,59,59,60,44,44,44,59,45,45,44,74,74,74,74,59,59,59,74,74,44,59,59,59,60,59,59,60,74,74,74,44,44,60,60,60,44,75,59,59,59,45,45,44,74,59,59,59,59,59,60,60,60,44,44,59,59,30,60,44,44,74,44,44,29,45,45,74,74,45,30,44,75,75,59,59,59,44,60,60,60,44,44,75,75,30,30,45,74,74,44,75,59,59,60,59,59,60,59,59,59,43,60,75,75,59,59,60,74,74,44,45,45,44,59,59,59,60,75,75,59,44,44,75,43,60,60,29,29,44,73,73,59,74,73,73,74,60,74,74,45,75,60,60,44,59,74,74,74,74,74,60,59,59,59,29,44,44,29,60,60,60,90,90,44,75,75,44,59,59,59,44,59,59,59,59,29,29,29,59,29,44,44,59,44,74,74,44,59,59,59,59,44,44,90,59,75,59,44,44,44,60,60,60,44,60,60,74,59,59,59,59,59,74,74,59,59,59,59,59,57,74,59,74,74,60,60,60,60,75,75,44,75,44,44,59,44,74,74,73,59,59,59,44,60,59,59,59,59,59,30,30,59,60,60,45,44,44,74,74,59,59,60,59,59,44,44,74,74,59,59,59,75,44,44,59,73,59,59,75,45,45,45,45,44,90,90,59,74,74,74,44,59,59,44,60,60,30,44,60,60,44,44,89,89,59,74,74,60,74,74,59,60,60,60,59,43,60,60,60,44,44,59,60,59,59,44,45,45,74,45,60,60,60,75,75,75,75,75,74,60,60,60,59,59,59,75,60,60,60,60,60,60,59,73,73,45,74,29,29,59,60,60,74,74,89,89,59,29,29,60,73,73,75,45,45,60,59,59,74,30,91,91,59,59,59,59,60,59,59,74,44,44,44,59,59,59,59,45,29,29,59,75,75,74,90,90,74,59,59,60,44,44,90,59,59,59,60,60,59,59,59,75,75,59,75,75,59,59,59,60,90,90,59,75,75,74,57,74,75,74,74,75,59,59,59,74,74,74,60,59,59,59,59,90,90,59,59,29,59,59,59,90,106,59,59,59,44,59,59,59,29,29,59,60,59,59,59,90,90,74,90,59,59,74,75,75,59,59,58,75,43,59,59,59,59,59,59,59,59,60,91,91,60,60,60,60,59,44,44,59,60,59,59,59,74,74,59,60,59,59,59,59,44,44,44,59,59,59,59,59,44,44,44,74,44,59,59,59,44,44,44,59,45,60,60,74,60,60,74,59,59,75,60,60,74,74,74,74,44,44,44,45,14,60,60,29,59,59,59,44,60,60,75,29,29,60,60,60,75,60,60,45,45,59,59,44,74,74,59,28,74,74,60,59,59,59,44,74,74,60,75,75,75,60,60,59,59,59,75,75,75,75,60,45,45,59,74,45,45,29,59,59,75,43,43,76,60,60,60,44,44,29,74,74,60,59,59,59,45,59,60,60,60,74,44,44,75,75,75,60,90,90,59,58,75,60,60,60,74,74,74,59,44,74,74,44,75,75,59,74,43,60,59,74,74,60,91,91,45,60,60,74,43,60,60,45,29,29,75,59,59,59,60,59,59,59,59,59,90,90,75,59,59,60,74,74,74,60,60,45,74,74,59,45,44,44,60,75,59,59,59,59,59,59,59,75,59,59,59,59,59,60,74,74,74,59,59,74,59,59,59,58,44,44,74,59,59,60,45,29,29,59,59,75,75,74,75,75,44,74,74,75,59,59,59,45,59,59,59,59,59,59,29,30,30,74,90,90,75,28,45,59,59,59,74,59,59,59,60,60,73,30,30,30,29,75,75,44,44,58,58,74,59,75,75,75,59,60,60,74,59,59,59,44,74,74,59,60,60,14,74,43,60,60,43,60,44,45,45,74,45,59,59,59,59,59,59,58,59,75,75,75,74,59,59,74,74,74,74,73,73,59,44,44,0,0,0,119,73,90,90,74,90,73,44,60,60,90,105,75,75,74,90,74,74,74,60,59,74,44,74,59,89,59,74,59,59,60,90,89,89,90,59,29,59,29,44,75,58,75,90,89,74,74,44,60,44,45,74,74,59,89,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,44,44,44,44,44,29,29,76,76,59,59,59,89,89,60,60,29,29,60,60,44,60,74,74,59,59,29,29,44,44,14,44,45,76,45,29,60,61,61,90,60,62,74,30,59,59,44,44,44,59,59,61,61,75,75,90,75,60,30,44,44,43,45,74,29,29,29,74,74,44,44,91,76,89,44,45,30,29,61,44,44,30,45,44,59,44,59,75,74,60,59,59,60,45,75,75,44,45,59,59,59,29,60,44,59,29,74,45,44,30,29,74,29,60,60,60,60,60,75,75,59,30,74,44,74,44,44,58,58,30,30,44,44,29,44,44,44,89,59,74,74,60,88,29,29,44,29,29,74,45,28,28,14,44,28,45,29,60,60,44,44,44,29,30,74,58,59,29,60,60,60,60,59,59,59,59,60,60,60,60,44,89,89,74,59,74,59,44,44,45,29,59,45,30,30,60,60,45,45,29,29,75,75,59,60,59,60,90,75,44,44,75,77,75,77,75,60,60,91,76,29,59,59,75,30,60,30,45,44,30,44,44,74,77,58,59,30,30,29,29,29,76,76,29,29,60,30,59,59,59,59,29,29,29,29,44,44,44,44,44,44,44,44,44,60,59,44,61,45,89,75,29,30,59,30,90,45,29,74,44,44,76,44,61,44,43,30,43,59,-120,93,25,74,75,58,59,90,60,29,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,22,22,22,22,37,37,6,21,22,22,29,29,23,23,43,43,22,22,22,22,6,22,22,22,23,23,6,23,43,7,0,0,45,60,45,44,45,44,22,22,6,23,45,45,31,22,13,22,14,6,0,0,0,0,0,0,0,0,22,22,22,70,7,23,22,37,30,30,6,6,7,23,7,7,6,6,23,23,23,23,43,60,22,21,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,37,37,37,37,37,37,37,37,19,19,19,19,19,53,53,53,53,53,54,54,54,54,54,19,54,21,21,21,21,51,51,51,22,22,21,37,22,21,22,22,44,45,22,38,21,21,7,7,21,22,45,45,45,45,45,45,44,44,44,45,44,44,22,22,6,6,22,22,6,6,22,55,6,38,7,7,7,7,45,45,6,6,7,7,7,7,23,23,60,60,22,22,6,22,7,6,22,23,22,21,6,22,6,22,21,38,21,21,22,22,22,22,22,45,44,29,28,59,28,27,6,6,22,22,21,22,22,22,22,38,23,22,21,21,6,6,22,52,37,68,68,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,22,22,38,15,15,15,90,15,15,15,91,15,15,15,15,92,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,95,95,15,90,95,20,20,29,59,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,58,58,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,60,59,60,61,60,60,0,0,0,0,0,0,0,0,94,94,8,9,8,8,8,8,8,9,8,8,8,8,8,8,8,8,24,10,8,8,8,8,8,8,8,8,8,8,8,8,8,8,24,8,8,8,8,8,8,9,8,8,8,8,8,8,8,8,8,8,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,94,92,0,0,0,0,0,0,0,0,0,-120,105,75,106,75,73,106,76,90,90,90,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,45,45,59,76,59,60,60,60,60,61,44,45,59,45,44,60,59,45,75,60,60,45,59,44,76,75,75,44,44,60,45,59,29,60,45,60,45,45,90,74,59,58,58,90,58,58,60,-120,58,58,29,58,59,45,75,59,44,76,29,29,45,45,60,29,43,44,60,61,45,29,59,10,10,10,94,10,10,10,94,94,94,94,94,94,0,0,0,0,0,0,0,0,0,0,0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,47,27,74,61,60,60,59,46,60,46,44,44,44,47,29,45,31,31,31,60,62,44,60,46,46,46,28,45,14,44,30,30,45,45,30,76,61,45,30,45,45,93,95,95,95,94,95,10,10,94,93,95,10,93,95,0,0,0,0,0,0,0,0,0,59,43,45,93,45,60,30,44,30,77,44,29,90,95,0,0,75,43,46,46,29,57,76,77,45,45,0,0,43,-120,105,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,22,22,22,22,22,22,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,23,7,0,0,0,0,0,53,22,23,6,45,45,45,45,77,13,45,45,23,23,23,23,23,23,23,23,23,22,22,22,36,38,0,22,36,22,22,38,0,23,0,37,22,0,22,22,0,22,22,22,23,22,36,23,22,22,22,38,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,5,7,22,23,22,23,22,23,22,23,22,23,22,23,6,7,6,7,6,7,6,7,6,7,6,7,6,7,6,7,22,23,22,23,5,7,37,39,37,23,5,7,22,23,6,7,23,23,23,23,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,22,23,6,7,22,23,22,23,22,23,7,22,23,22,23,22,23,22,23,5,7,5,7,21,23,22,23,23,23,23,23,7,7,23,23,21,23,6,22,23,6,22,23,5,7,6,6,23,22,22,6,22,22,23,23,23,22,22,6,23,22,22,6,23,22,22,22,23,22,23,22,22,23,23,23,23,45,23,45,23,23,7,45,22,23,23,22,23,6,23,7,7,7,20,22,22,22,20,22,22,6,22,22,6,6,21,59,59,23,23,23,21,22,22,22,22,22,23,22,22,22,22,22,23,22,22,22,21,23,23,22,22,22,23,23,23,22,22,23,22,22,22,22,22,21,23,7,23,7,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,39,7,39,7,7,21,22,22,22,7,23,23,23,23,23,23,23,23,23,23,23,23,23,6,6,6,6,7,5,5,5,6,7,6,6,6,6,7,6,6,7,6,7,6,7,13,13,13,7,13,13,11,13,13,13,11,23,7,6,5,6,5,6,6,6,4,6,4,6,6,6,6,5,5,5,5,6,6,6,6,6,6,5,5,5,6,7,7,7,6,6,6,6,6,7,7,7,7,7,7,7,7,7,15,15,15,15,7,7,7,7,7,7,7,7,7,7,23,23,22,22,22,22,30,30,30,30,22,22,23,23,22,22,23,23,23,23,23,23,7,23,7,7,7,7,23,23,23,23,23,23,31,31,31,31,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,13,13,13,13,13,13,7,15,15,15,15,15,15,7,7,55,55,30,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,13,31,13,13,13,13,29,13,31,13,31,31,15,15,15,15,14,14,31,14,15,15,31,15,14,15,31,14,31,14,15,15,14,31,14,14,15,31,47,14,15,47,15,15,31,13,31,31,63,47,47,12,31,31,13,47,12,13,13,15,13,13,13,13,0,0,13,14,14,12,31,31,12,15,31,31,63,14,15,15,15,15,15,31,31,15,15,15,15,15,15,15,31,31,31,31,31,31,31,15,13,12,15,15,12,15,12,31,31,47,15,15,15,31,15,15,15,14,15,15,0,0,0,0,0,0,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,30,44,46,29,62,30,15,30,30,30,15,31,46,15,0,0,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,-51,-84,-83,-51,-51,-35,-115,45,45,-51,0,0,0,0,0,0,23,6,23,6,23,6,7,0,0,0,0,0,0,0,0,0,120,-120,-120,17,2,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,30,-100,-99,30,30,46,30,15,15,46,30,15,104,104,120,0,104,120,104,-120,89,120,120,121,104,121,104,89,105,104,89,105,121,104,105,0,105,89,105,105,0,0,0,0,37,7,38,53,37,0,37,7,21,7,37,7,21,7,37,7,37,23,7,36,23,22,23,53,23,22,23,5,7,68,39,22,23,5,7,38,39,22,23,5,7,22,23,5,7,22,23,6,7,22,23,5,7,22,23,5,7,22,23,22,23,22,23,22,23,23,23,6,7,23,23,6,7,23,23,6,7,23,23,6,7,22,23,6,7,22,23,6,7,22,23,6,7,22,23,6,7,22,23,5,7,22,23,5,7,22,23,6,7,22,23,5,7,38,39,6,7,22,23,5,7,37,39,6,7,22,23,22,23,22,23,5,7,6,7,22,23,38,23,38,23,0,0,15,0,87,90,44,59,29,30,104,-100,54,59,61,52,46,52,29,60,91,76,60,61,76,59,59,60,59,120,120,44,45,61,75,45,46,60,44,60,60,76,46,45,74,77,61,60,29,29,45,77,46,61,60,44,45,45,30,44,44,60,-116,29,55,89,15,104,76,75,74,75,74,75,75,76,121,73,59,104,61,76,75,75,75,91,91,58,76,59,45,59,76,75,-116,-120,55,45,60,60,19,54,37,20,52,22,22,22,22,22,22,22,22,37,22,37,7,6,22,7,7,22,7,22,7,22,7,23,7,6,22,22,7,7,7,54,7,7,22,7,22,7,22,22,7,7,7,37,7,6,6,7,7,22,22,38,7,39,22,22,23,36,36,15,22,21,23,22,39,39,22,21,37,22,6,6,6,6,7,6,21,22,6,23,21,7,21,21,7,21,22,22,22,6,0,0,0,36,36,36,36,36,21,0,0,36,21,21,22,6,21,0,0,21,21,37,22,37,21,0,0,22,36,68,0,0,0,59,47,45,60,-120,61,46,0,51,22,38,22,38,22,22,0,0,0,0,0,0,0,0,0,0,15,15,15,15,22,15,15],"non-unicode":"ÀÁÂÈÊËÍÓÔÕÚßãõğİıŒœŞşŴŵžȇ\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αβΓπΣσμτΦΘΩδ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\u0000"}